From baccbd8f90b39f4308f2f761fd73af10804e51fd Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 9 Dec 2019 15:21:27 +0100 Subject: [PATCH 001/112] Add basic definitions --- .../tools/dotc/transform/init/Effects.scala | 45 ++++++++++++++ .../dotc/transform/init/Potentials.scala | 59 +++++++++++++++++++ .../tools/dotc/transform/init/Summary.scala | 22 +++++++ 3 files changed, 126 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Effects.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Potentials.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Summary.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala new file mode 100644 index 000000000000..e738db8ad313 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc +package transform +package init + +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Contexts._ +import scala.language.implicitConversions + +import Potentials._ + +object Effects { + type Effects = Set[Effect] + val empty: Effects = Set.empty + + def show(effs: Effects)(implicit ctx: Context): String = + effs.map(_.show).mkString(", ") + + sealed trait Effect { + def size: Int + def show(implicit ctx: Context): String + def source: Tree + } + + case class Leak(potential: Potential)(val source: Tree) extends Effect { + def size: Int = potential.size + def show(implicit ctx: Context): String = + potential.show + "↑" + } + + case class FieldAccess(potential: Potential, field: Symbol)(val source: Tree) extends Effect { + def size: Int = potential.size + def show(implicit ctx: Context): String = + potential.show + "." + field.name.show + "!" + } + + case class MethodCall(potential: Potential, method: Symbol, virtual: Boolean)(val source: Tree) extends Effect { + def size: Int = potential.size + def show(implicit ctx: Context): String = { + val modifier = if (virtual) "" else "(static)" + potential.show + "." + method.name.show + "!" + modifier + } + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala new file mode 100644 index 000000000000..cdf6b90af8a9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -0,0 +1,59 @@ +package dotty.tools.dotc +package transform +package init + +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Contexts._ +import scala.language.implicitConversions + +import Effects._ + +object Potentials { + type Potentials = Set[Potential] + val empty: Potentials = Set.empty + + def show(pots: Potentials)(implicit ctx: Context): String = + pots.map(_.show).mkString(", ") + + sealed trait Potential { + def size: Int + def show(implicit ctx: Context): String + def source: Tree + } + + case class ThisRef(cls: ClassSymbol)(val source: Tree) extends Potential { + val size: Int = 1 + def show(implicit ctx: Context): String = cls.name.show + ".this" + } + + case class Warm(cls: ClassSymbol, outers: Map[ClassSymbol, Potentials])(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" + } + + case class FieldValue(potential: Potential, field: Symbol)(val source: Tree) extends Potential { + def size: Int = potential.size + def show(implicit ctx: Context): String = potential.show + "." + field.name.show + } + + case class MethodReturn(potential: Potential, symbol: Symbol, virtual: Boolean)(val source: Tree) extends Potential { + def size: Int = potential.size + 1 + def show(implicit ctx: Context): String = { + val modifier = if (virtual) "" else "(static)" + potential.show + "." + symbol.name.show + modifier + } + } + + case class Cold(cls: ClassSymbol, definite: Boolean)(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = "Cold[" + cls.show + "]" + } + + case class Fun(potentials: Potentials, effects: Effects)(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = + "Fun[pots = " + potentials.map(_.show).mkString(";") + ", effs = " + effects.map(_.show).mkString(";") + "]" + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala new file mode 100644 index 000000000000..8d0d0989f0cd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -0,0 +1,22 @@ +package dotty.tools.dotc +package transform +package init + +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import ast.tpd._ +import Symbols._ + +import Potentials._, Effects._ + +object Summary { + type Summary = (Potentials, Effects) + val empty: Summary = (Potentials.empty, Effects.empty) + + def show(summary: Summary)(implicit ctx: Context): String = { + val pots = Potentials.show(summary._1) + val effs = Effects.show(summary._2) + s"([$pots], [$effs])" + } +} From 337c4b632f25173e87e05240ba926bc0baaa48d9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 9 Dec 2019 16:57:27 +0100 Subject: [PATCH 002/112] Add Env definition --- .../dotty/tools/dotc/config/Printers.scala | 1 + .../tools/dotc/transform/init/Effects.scala | 9 ++-- .../dotty/tools/dotc/transform/init/Env.scala | 48 +++++++++++++++++++ .../dotc/transform/init/Potentials.scala | 9 ++-- .../tools/dotc/transform/init/Summary.scala | 3 -- 5 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Env.scala diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 3536302bd865..fe4a2ae92af3 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -28,6 +28,7 @@ object Printers { val implicits: Printer = noPrinter val implicitsDetailed: Printer = noPrinter val lexical: Printer = noPrinter + val init: Printer = noPrinter val inlining: Printer = noPrinter val interactiv: Printer = noPrinter val nullables: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index e738db8ad313..d448ce9ebbd3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -2,11 +2,10 @@ package dotty.tools.dotc package transform package init -import dotty.tools.dotc.ast.tpd._ -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Contexts._ -import scala.language.implicitConversions +import ast.tpd._ +import core.Types._ +import core.Symbols._ +import core.Contexts._ import Potentials._ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala new file mode 100644 index 000000000000..d00a2135380f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import ast.Trees._ +import ast.tpd +import Contexts.Context +import Symbols._ +import reporting.trace +import config.Printers.init + +import scala.collection.mutable.Map + +import Effects._, Potentials._, Summary._ + +implicit def theCtx(implicit env: Env): Context = env.ctx + +case class Env(ctx: Context, summaryCache: Map[Symbol, Summary]) { + private implicit def self: Env = this + + def withCtx(newCtx: Context): Env = this.copy(ctx = newCtx) + + def cachePotentialsFor(symbol: Symbol, pots: Potentials): Unit = { + summaryCache(symbol) = (pots, Effects.empty) + } + + /** Summary of a method or field */ + def summaryOf(symbol: Symbol): Summary = + if (summaryCache.contains(symbol)) summaryCache(symbol) + else trace("summary for " + symbol.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val tree: tpd.Tree = ??? // getRhs(symbol) + val summary = + if (tree.isEmpty) + Summary.empty + else if (symbol.isConstructor) + ??? // analyzeConstructor(symbol, tree)(this.withCtx(ctx.withOwner(symbol))) + else + ??? // analyze(tree)(this.withCtx(ctx.withOwner(symbol))) + + summaryCache(symbol) = summary + summary + } + + def effectsOf(symbol: Symbol): Effects = summaryOf(symbol)._2 + + def potentialsOf(symbol: Symbol): Potentials = summaryOf(symbol)._1 +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index cdf6b90af8a9..93a7ebf82f6b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -2,11 +2,10 @@ package dotty.tools.dotc package transform package init -import dotty.tools.dotc.ast.tpd._ -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Contexts._ -import scala.language.implicitConversions +import ast.tpd._ +import core.Types._ +import core.Symbols._ +import core.Contexts._ import Effects._ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 8d0d0989f0cd..c466b0d5b867 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -2,11 +2,8 @@ package dotty.tools.dotc package transform package init -import dotty.tools.dotc._ import core._ import Contexts.Context -import ast.tpd._ -import Symbols._ import Potentials._, Effects._ From 52c844183a7e99820826b11ee8ef56d20b877141 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 10:22:01 +0100 Subject: [PATCH 003/112] Add SuperRef as potential --- .../src/dotty/tools/dotc/transform/init/Potentials.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 93a7ebf82f6b..3acf24dd54ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -27,6 +27,11 @@ object Potentials { def show(implicit ctx: Context): String = cls.name.show + ".this" } + case class SuperRef(cls: ClassSymbol, supercls: ClassSymbol)(val source: Tree) extends Potential { + val size: Int = 1 + def show(implicit ctx: Context): String = cls.name.show + ".super[" + supercls.name.show + "]" + } + case class Warm(cls: ClassSymbol, outers: Map[ClassSymbol, Potentials])(val source: Tree) extends Potential { def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" From 91baf285bfcc879f93ed5fbe75e26a0062556f59 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 10:27:49 +0100 Subject: [PATCH 004/112] Add phase SetDefTree --- .../dotc/transform/init/SetDefTree.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala new file mode 100644 index 000000000000..564b6b6290ac --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala @@ -0,0 +1,33 @@ +package dotty.tools.dotc +package transform +package init + +import MegaPhase._ +import ast.tpd +import core.Contexts.Context + +/** Set the `defTree` property of symbols */ +class SetDefTree extends MiniPhase { + import tpd._ + + override val phaseName: String = SetDefTree.name + override val runsAfter = Set("flatten", "lambdaLift", Erasure.name) + + // don't allow plugins to change tasty + // research plugins can still change the phase plan at will + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + val ctx2 = ctx.fresh.setSetting(ctx.settings.YretainTrees, true) + super.runOn(units)(ctx2) + } + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = tree.setDefTree +} + +object SetDefTree { + val name: String = "SetDefTree" +} From 68ec7ecb4b4cf7658d3839c0b3e8c190adaf307b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 11:55:53 +0100 Subject: [PATCH 005/112] Add summarization of expression --- .../dotc/transform/init/Potentials.scala | 26 +- .../dotc/transform/init/Summarization.scala | 262 ++++++++++++++++++ .../tools/dotc/transform/init/Summary.scala | 15 + .../tools/dotc/transform/init/Util.scala | 12 + 4 files changed, 310 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Summarization.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Util.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 3acf24dd54ca..cef2f8129954 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -3,11 +3,10 @@ package transform package init import ast.tpd._ -import core.Types._ -import core.Symbols._ -import core.Contexts._ +import core._ +import Types._, Symbols._, Contexts._ -import Effects._ +import Effects._, Summary._ object Potentials { type Potentials = Set[Potential] @@ -37,7 +36,7 @@ object Potentials { def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" } - case class FieldValue(potential: Potential, field: Symbol)(val source: Tree) extends Potential { + case class FieldReturn(potential: Potential, field: Symbol)(val source: Tree) extends Potential { def size: Int = potential.size def show(implicit ctx: Context): String = potential.show + "." + field.name.show } @@ -60,4 +59,21 @@ object Potentials { def show(implicit ctx: Context): String = "Fun[pots = " + potentials.map(_.show).mkString(";") + ", effs = " + effects.map(_.show).mkString(";") + "]" } + + // --------- operations on potentials --------- + + def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true, bindings: Map[Symbol, Potentials] = Map.empty)(implicit ctx: Context): Summary = + ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => + if (pot.size > 3) + (pots, effs + Leak(pot)(source)) + else if (symbol.is(Flags.Method)) + ( + pots + MethodReturn(pot, symbol, virtual)(source), + effs + MethodCall(pot, symbol, virtual)(source) + ) + else + (pots + FieldReturn(pot, symbol)(source), effs + FieldAccess(pot, symbol)(source)) + } + + def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala new file mode 100644 index 000000000000..e266e3da0acd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -0,0 +1,262 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import ast.tpd._ +import Decorators._ +import Symbols._ +import Constants.Constant +import Types._ +import config.Printers.init +import reporting.trace + +import Effects._, Potentials._, Summary._, Util._ + +object Summarization { + private val ignoredMethods = Set( + // "dotty.runtime.LazyVals$.setFlag", + // "dotty.runtime.LazyVals$.get", + // "dotty.runtime.LazyVals$.CAS", + // "dotty.runtime.LazyVals$.wait4Notification", + "scala.runtime.EnumValues.register", + "java.lang.Object.isInstanceOf", + "java.lang.Object.getClass", + "java.lang.Object.eq", + "java.lang.Object.ne" + ) + + /** Summarization of potentials and effects for an expression + * + * Optimization: + * + * 1. potentials for expression of primitive value types can be + * safely abandoned, as they are always fully initialized. + */ + def analyze(expr: Tree)(implicit ctx: Context): Summary = + trace("summarizing " + expr.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val summary: Summary = expr match { + case Ident(name) => + assert(name.isTermName, "type trees should not reach here") + analyze(expr.tpe, expr) + + case supert: Super => + val SuperType(thisTp, superTp) = supert.tpe.asInstanceOf[SuperType] + val pot = SuperRef(thisTp.classSymbol.asClass, superTp.classSymbol.asClass)(supert) + Summary.empty + pot + + case Select(qualifier, name) => + val (pots, effs) = analyze(qualifier) + val (pots2, effs2) = pots.select(expr.symbol, expr) + (pots2, effs ++ effs2) + + case _: This => + val cls = expr.tpe.widen.classSymbol.asClass + Summary.empty + ThisRef(cls)(expr) + + case Apply(fun, args) => + val summary = analyze(fun) + args.foldLeft(summary) { (sum, arg) => + val (pots1, effs1) = analyze(arg) + sum.withEffs(pots1.leak(arg) ++ effs1) + } + + case TypeApply(fun, args) => + analyze(fun) + + case Literal(const) => + Summary.empty + + case New(tpt) => + ??? + + case Typed(expr, tpt) => + analyze(expr) + + case NamedArg(name, arg) => + analyze(arg) + + case Assign(lhs, rhs) => + val (pots, effs) = analyze(rhs) + (Potentials.empty, pots.leak(expr) ++ effs) + + case closureDef(ddef) => // must be before `Block` + analyze(ddef.rhs) + + case Block(stats, expr) => + val effs = stats.foldLeft(Effects.empty) { (acc, stat) => acc ++ analyze(stat)._2 } + val (pots2, effs2) = analyze(expr) + (pots2, effs ++ effs2) + + case If(cond, thenp, elsep) => + val (pots0, effs0) = analyze(cond) + val (pots1, effs1) = analyze(thenp) + val (pots2, effs2) = analyze(elsep) + (pots0 ++ pots1 ++ pots2, effs0 ++ effs1 ++ effs2) + + case Annotated(arg, annot) => + if (expr.tpe.hasAnnotation(defn.UncheckedAnnot)) Summary.empty + else analyze(arg) + + case Match(selector, cases) => + // possible for switches + val (pots, effs) = analyze(selector) + cases.foldLeft((Potentials.empty, pots.leak(selector) ++ effs)) { (acc, cas) => + acc union analyze(cas.body) + } + + // case CaseDef(pat, guard, body) => + // Summary.empty + + case Return(expr, from) => + // TODO: return potential to the method + analyze(expr) + + case WhileDo(cond, body) => + // for lazy fields, the translation may result im `while ()` + val (_, effs1) = if (cond.isEmpty) Summary.empty else analyze(cond) + val (_, effs2) = analyze(body) + (Potentials.empty, effs1 ++ effs2) + + case Labeled(_, expr) => + val (_, effs1) = analyze(expr) + (Potentials.empty, effs1) + + case Try(block, cases, finalizer) => + val (pots, effs) = cases.foldLeft(analyze(block)) { (acc, cas) => + acc union analyze(cas.body) + } + val (_, eff2) = if (finalizer.isEmpty) Summary.empty else analyze(finalizer) + (pots, effs ++ eff2) + + case SeqLiteral(elems, elemtpt) => + val effsAll: Effects = elems.foldLeft(Effects.empty) { (effs, elem) => + val (pots1, effs1) = analyze(elem) + pots1.leak(expr) ++ effs1 ++ effs + } + (Potentials.empty, effsAll) + + case Inlined(call, bindings, expansion) => + // TODO: bindings + analyze(expansion) + + case tree @ ValDef(name, tpt, _) => + assert(!tree.symbol.owner.isClass, "unexpected ValDef") + val (pots, effs) = analyze(tree.rhs) + (Potentials.empty, pots.leak(expr) ++ effs) + + case Thicket(List()) => + // possible in try/catch/finally, see tests/crash/i6914.scala + Summary.empty + + case _ => + throw new Exception("unexpected tree: " + expr.show) + } + + if (isDefiniteHot(expr.tpe)) (Potentials.empty, summary._2) + else summary + } + + private def isDefiniteHot(tp: Type)(implicit ctx: Context): Boolean = { + val sym = tp.widen.finalResultType.typeSymbol + sym.isPrimitiveValueClass || sym == defn.StringClass + } + + def analyze(tp: Type, source: Tree)(implicit ctx: Context): Summary = + trace("summarizing " + tp.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val summary: Summary = tp match { + case tmref: TermRef if tmref.prefix == NoPrefix => + Summary.empty + case tmref: TermRef => + val (pots, effs) = analyze(tmref.prefix, source) + val (pots2, effs2) = pots.select(tmref.symbol, source) + (pots2, effs ++ effs2) + case ThisType(tref: TypeRef) if tref.classSymbol.is(Flags.Package) => + Summary.empty + case thisTp: ThisType => + val cls = thisTp.widen.classSymbol.asClass + Summary.empty + ThisRef(cls)(source) + case SuperType(thisTp, superTp) => + val pot = SuperRef(thisTp.classSymbol.asClass, superTp.classSymbol.asClass)(source) + Summary.empty + pot + } + + if (isDefiniteHot(tp)) (Potentials.empty, summary._2) + else summary + } + + /** Constructors need special treatment to distinguish assignment from initialization + * + * This is possible thanks to the convention in Dotty that assignment will be method + * calls, while field initialization will be assignments at the phase genBCode. + */ + def analyzeConstructor(ctor: Symbol, tree: Tree)(implicit ctx: Context): Summary = + trace("summarizing constructor" + tree.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val block = tree.asInstanceOf[Block] + val effsAll = (block.expr :: block.stats).foldLeft(Effects.empty) { case (acc, stat) => + stat match { + case Assign(lhs, rhs) => + // no leaking for initialization + val (_, effs) = analyze(rhs) + acc ++ effs + + case tree => + val (_, effs) = analyze(tree) + acc ++ effs + } + } + val pots = + if (ctor.isPrimaryConstructor) Potentials.empty + else analyze(block.stats(0))._1 + + (pots, effsAll) + } + + def getRhs(symbol: Symbol)(implicit ctx: Context): Tree = { + def notFound() = { + traceIndented("Not tree found for " + symbol, init) + EmptyTree + } + + if (symbol.isAllOf(Flags.Lazy | Flags.Method, butNot = Flags.Lifted)) { + if (symbol.defTree.isEmpty) { + notFound() + return symbol.defTree + } + + var rhs: Tree = symbol.defTree.asInstanceOf[DefDef].rhs + symbol.defTree.foreachSubTree { + case vdef: ValDef if vdef.symbol.is(Flags.Synthetic) && vdef.name.toString == "result" => + rhs = vdef.rhs + case _ => + } + + rhs + } + else if (symbol.is(Flags.Method)) + // give up if cross project boundary + if (symbol.defTree.isEmpty) notFound() + else symbol.defTree.asInstanceOf[DefDef].rhs + else if (symbol.isAllOf(Flags.Module | Flags.StableRealizable, butNot = Flags.Method)) + // static fields + EmptyTree + else // rhs for fields are `assign` in ctor of its definition class + symbol.owner.defTree match { + case TypeDef(_, Template(ctor, _, _, _)) => + ctor.rhs match { + case Block(stats, expr) => + stats.collectFirst { + case Assign(sel @ Select(_: This, _), rhs) if sel.symbol == symbol => rhs + case Assign(ident, rhs) if ident.symbol == symbol => rhs + } match { + case Some(init) => init + case None => + notFound() + } + case _ => + notFound() + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index c466b0d5b867..edeb8ebc20dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -16,4 +16,19 @@ object Summary { val effs = Effects.show(summary._2) s"([$pots], [$effs])" } + + def (summary1: Summary) union (summary2: Summary): Summary = + (summary1._1 ++ summary2._1, summary1._2 ++ summary2._2) + + def (summary: Summary) + (pot: Potential): Summary = + (summary._1 + pot, summary._2) + + def (summary: Summary) + (eff: Effect): Summary = + (summary._1, summary._2 + eff) + + def (summary: Summary) withPots (pots: Potentials): Summary = + (summary._1 ++ pots, summary._2) + + def (summary: Summary) withEffs (effs: Effects): Summary = + (summary._1, summary._2 ++ effs) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala new file mode 100644 index 000000000000..7c41f1389565 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -0,0 +1,12 @@ +package dotty.tools.dotc +package transform +package init + +import core.Contexts._ +import config.Printers.Printer + + +object Util { + def traceIndented(msg: String, printer: Printer)(implicit ctx: Context): Unit = + printer.println(s"${ctx.base.indentTab * ctx.base.indent} $msg") +} \ No newline at end of file From f47577afeb0d7cb93cc4bce49a0dc3e020ee6f63 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 12:06:11 +0100 Subject: [PATCH 006/112] Add documentation for Warm --- .../src/dotty/tools/dotc/transform/init/Potentials.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index cef2f8129954..e2a746835345 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -31,6 +31,12 @@ object Potentials { def show(implicit ctx: Context): String = cls.name.show + ".super[" + supercls.name.show + "]" } + /** A warm potential represents an object of which all fields are initialized, but it may contain + * reference to objects under initialization. + * + * @param cls The concrete class of the object + * @param outers The potentials for the immdiate outer `this`. One entry for each class in the inheritance hierarchy. + */ case class Warm(cls: ClassSymbol, outers: Map[ClassSymbol, Potentials])(val source: Tree) extends Potential { def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" From fa6c1829fec57f6d47d78c144a075d5ee5bdd217 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 12:58:37 +0100 Subject: [PATCH 007/112] Tweak summariztion --- .../dotty/tools/dotc/transform/init/Env.scala | 13 ++- .../dotc/transform/init/Summarization.scala | 83 ++++--------------- 2 files changed, 22 insertions(+), 74 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index d00a2135380f..fb9925540576 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -29,14 +29,13 @@ case class Env(ctx: Context, summaryCache: Map[Symbol, Summary]) { def summaryOf(symbol: Symbol): Summary = if (summaryCache.contains(symbol)) summaryCache(symbol) else trace("summary for " + symbol.show, init, s => Summary.show(s.asInstanceOf[Summary])) { - val tree: tpd.Tree = ??? // getRhs(symbol) val summary = - if (tree.isEmpty) - Summary.empty - else if (symbol.isConstructor) - ??? // analyzeConstructor(symbol, tree)(this.withCtx(ctx.withOwner(symbol))) - else - ??? // analyze(tree)(this.withCtx(ctx.withOwner(symbol))) + if (symbol.isConstructor) + Summarization.analyzeConstructor(symbol) + else if (symbol.is(Flags.Method)) + Summarization.analyzeMethod(symbol) + else // field + Summarization.analyzeField(symbol) summaryCache(symbol) = summary summary diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index e266e3da0acd..08c2c1eab0ff 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -186,77 +186,26 @@ object Summarization { else summary } - /** Constructors need special treatment to distinguish assignment from initialization - * - * This is possible thanks to the convention in Dotty that assignment will be method - * calls, while field initialization will be assignments at the phase genBCode. - */ - def analyzeConstructor(ctor: Symbol, tree: Tree)(implicit ctx: Context): Summary = - trace("summarizing constructor" + tree.show, init, s => Summary.show(s.asInstanceOf[Summary])) { - val block = tree.asInstanceOf[Block] - val effsAll = (block.expr :: block.stats).foldLeft(Effects.empty) { case (acc, stat) => - stat match { - case Assign(lhs, rhs) => - // no leaking for initialization - val (_, effs) = analyze(rhs) - acc ++ effs - - case tree => - val (_, effs) = analyze(tree) - acc ++ effs - } - } - val pots = - if (ctor.isPrimaryConstructor) Potentials.empty - else analyze(block.stats(0))._1 + def analyzeMethod(sym: Symbol)(implicit ctx: Context): Summary = { + val ddef = sym.defTree.asInstanceOf[DefDef] + analyze(ddef.rhs)(ctx.withOwner(sym)) + } - (pots, effsAll) + def analyzeField(sym: Symbol)(implicit ctx: Context): Summary = { + val vdef = sym.defTree.asInstanceOf[ValDef] + analyze(vdef.rhs)(ctx.withOwner(sym)) } - def getRhs(symbol: Symbol)(implicit ctx: Context): Tree = { - def notFound() = { - traceIndented("Not tree found for " + symbol, init) - EmptyTree + /** Summarize secondary constructors or class body */ + def analyzeConstructor(ctor: Symbol)(implicit ctx: Context): Summary = + trace("summarizing constructor " + ctor.owner.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + if (ctor.isPrimaryConstructor) { + val tpl = ctor.owner.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] + analyze(Block(tpl.body, unitLiteral)) } - - if (symbol.isAllOf(Flags.Lazy | Flags.Method, butNot = Flags.Lifted)) { - if (symbol.defTree.isEmpty) { - notFound() - return symbol.defTree - } - - var rhs: Tree = symbol.defTree.asInstanceOf[DefDef].rhs - symbol.defTree.foreachSubTree { - case vdef: ValDef if vdef.symbol.is(Flags.Synthetic) && vdef.name.toString == "result" => - rhs = vdef.rhs - case _ => - } - - rhs + else { + val ddef = ctor.defTree.asInstanceOf[DefDef] + analyze(ddef.rhs)(ctx.withOwner(ctor)) } - else if (symbol.is(Flags.Method)) - // give up if cross project boundary - if (symbol.defTree.isEmpty) notFound() - else symbol.defTree.asInstanceOf[DefDef].rhs - else if (symbol.isAllOf(Flags.Module | Flags.StableRealizable, butNot = Flags.Method)) - // static fields - EmptyTree - else // rhs for fields are `assign` in ctor of its definition class - symbol.owner.defTree match { - case TypeDef(_, Template(ctor, _, _, _)) => - ctor.rhs match { - case Block(stats, expr) => - stats.collectFirst { - case Assign(sel @ Select(_: This, _), rhs) if sel.symbol == symbol => rhs - case Assign(ident, rhs) if ident.symbol == symbol => rhs - } match { - case Some(init) => init - case None => - notFound() - } - case _ => - notFound() - } - } } } From bb513dc4df4f0a9748553471f202a93749eb7911 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 13:03:59 +0100 Subject: [PATCH 008/112] Handle Lazy ValDef --- .../dotty/tools/dotc/transform/init/Summarization.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 08c2c1eab0ff..5cf277fdd8dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -142,9 +142,11 @@ object Summarization { analyze(expansion) case tree @ ValDef(name, tpt, _) => - assert(!tree.symbol.owner.isClass, "unexpected ValDef") - val (pots, effs) = analyze(tree.rhs) - (Potentials.empty, pots.leak(expr) ++ effs) + if (tree.symbol.is(Flags.Lazy)) Summary.empty + else { + val (pots, effs) = analyze(tree.rhs) + (Potentials.empty, pots.leak(expr) ++ effs) + } case Thicket(List()) => // possible in try/catch/finally, see tests/crash/i6914.scala From 891d6d86a4cd974cbc9dbaecc70944cdee8662bb Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 10 Dec 2019 13:05:12 +0100 Subject: [PATCH 009/112] Handle TypeDef and DefDef in summarization --- .../src/dotty/tools/dotc/transform/init/Summarization.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 5cf277fdd8dd..463b5a23cede 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -152,6 +152,9 @@ object Summarization { // possible in try/catch/finally, see tests/crash/i6914.scala Summary.empty + case _: TypeDef | _ : DefDef => + Summary.empty + case _ => throw new Exception("unexpected tree: " + expr.show) } From 1ac3356e14c4d129bb212ac382017cd61df9b7ff Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 00:00:39 +0100 Subject: [PATCH 010/112] WIP - add checking --- .../tools/dotc/transform/init/Checking.scala | 525 ++++++++++++++++++ .../dotc/transform/init/Potentials.scala | 2 +- .../dotc/transform/init/Summarization.scala | 8 +- 3 files changed, 530 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Checking.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala new file mode 100644 index 000000000000..0895f4736513 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -0,0 +1,525 @@ +package dotty.tools.dotc +package transform +package init + +import scala.collection.mutable + +import core._ +import Contexts.Context +import ast.tpd._ +import Decorators._ +import Symbols._ +import Constants.Constant +import Types._ +import util.NoSourcePosition + +import Effects._, Potentials._, Summary._, Util._ + +object Checking { + /** The checking state + * + * Why `visited` is a set of `MethodCall` instead of `Symbol`? Think the following program: + * + * class C(x: Int, a: A @cold) { + * val n = if (x > 0) new C(x - 1, a).m() else 0 + * val b: Int = this.m() + * def m(): Int = b + * } + * + */ + case class State( + visited: mutable.Set[Effect], + path: Vector[Tree], + currentClass: ClassSymbol, + inited: mutable.Set[Symbol], + parentInited: mutable.Map[ClassSymbol, Potentials], + env: Env + ) { + def withVisited(eff: Effect)(implicit ctx: Context): State = { + visited += eff + copy(path = this.path :+ eff.source) + } + + def trace(implicit state: State): String = { + var indentCount = 0 + var last = "" + val sb = new StringBuilder + this.path.foreach { tree => + indentCount += 1 + val pos = tree.sourcePos + val line = "[ " + pos.source.file.toString + ":" + (pos.line + 1) + " ]" + if (last != line) + sb.append( + if (pos.source.exists) + i"${ " " * indentCount }-> ${pos.lineContent.trim}\t$line\n" + else + i"${tree.show}\n" + ) + last = line + } + sb.toString + } + } + + private implicit def theEnv(implicit state: State): Env = state.env + + /** Check that the given concrete class may be initialized safely + * + * It assumes that all definitions are properly summarized before-hand. + * However, summarization can be done lazily on-demand to improve + * performance. + */ + def checkClassBody(cdef: TypeDef, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show) { + val tpl = cdef.rhs.asInstanceOf[Template] + + // mark current class as initialized + state.parentInited += cdef.symbol.asClass -> outer + + def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show) { + tree match { + case Apply(sel @ Select(_: This, _), _) if sel.symbol.isConstructor => + // ctor call inside 2nd ctor + val tree = sel.symbol.defTree + traceOp("check ctor: " + sel.symbol.show) { + if (!tree.isEmpty) doCheck(tree.asInstanceOf[DefDef].rhs)(ctx.withOwner(tree.symbol)) + } + + case vdef : ValDef => + val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) + theEnv.cachePotentialsFor(vdef.symbol, pots) + traceIndented(vdef.symbol.show + " initialized") + effs.foreach { check(_) } + state.inited += vdef.symbol + + case tree => + val (_, effs) = Summarization.analyze(tree) + effs.foreach { check(_)(state) } + } + } + + // check parent calls : follows linearization ordering + // see spec 5.1 about "Template Evaluation". + // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#class-linearization + + def checkStats(stats: List[Tree])(implicit ctx: Context): Unit = + stats.foreach { stat => + val (_, effs) = Summarization.analyze(stat) + effs.foreach { check(_) } + } + + tpl.parents.foreach { + case Block(stats, parent) => + val (ctor, _, argss) = decomposeCall(parent) + checkStats(stats) + checkStats(argss.flatten) + + case Apply(Block(stats, parent), args) => + val (ctor, _, argss) = decomposeCall(parent) + checkStats(stats) + checkStats(args) + checkStats(argss.flatten) + + case parent : Apply => + val (ctor, _, argss) = decomposeCall(parent) + checkStats(argss.flatten) + + case ref @ (_ : Ident | _ : Select) => + + } + + // check class body + tpl.body.foreach { checkClassBodyStat(_) } + } + + def checkSecondaryConstructor(ctor: Symbol, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + ctor.show) { + val Block(ctorCall :: stats, expr) = ctor.defTree + val ctorCallSym = ctorCall.symbol + + traceOp("check ctor: " + sel.symbol.show) { + if (ctorCallSym.isPrimaryConstructor) + checkClassBody(ctorCallSym.owner.defTree.asInstanceOf[TypeDef], outer) + else + checkSecondaryConstructor(ctorCallSym, outer) + } + + (stats :+ expr).foreach { + val (_, effs) = Summarization.analyze(tree)(ctx.withOwner(ctor)) + effs.foreach { check(_)(state) } + } + } + + + /** Resolve possible overriding of the term symbol `sym` relative to `thisClass` */ + private def resolveVirtual(thisClass: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = + if (sym.isEffectivelyFinal) sym + else sym.matchingMember(thisClass.typeRef) + + private def check(eff: Effect)(implicit state: State): Unit = + if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show) { + eff match { + case Leak(pot) => + pot match { + case ThisRef(tp) => + theCtx.warning( + "Leaking of this. Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + + case Cold(cls, _) => + theCtx.warning( + "Leaking of cold value " + eff.source.show + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + + case Warm(cls) => + theCtx.warning( + "Leaking of warm value " + eff.source.show + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + + case Dependent(cls, bindings) => + val state2 = state.withVisited(eff) + bindings.values.flatten.flatMap(pot => + substitute(pot, bindings, None) + ).foreach { pot => + check(Leak(pot)(eff.source))(state2) + } + + case Fun(pots, effs) => + val state2 = state.withVisited(eff) + effs.foreach { check(_)(state2) } + pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + + case pot => + val state2 = state.withVisited(eff) + val pots = expand(pot) + pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + } + + case FieldAccess(pot, field) => + pot match { + case ThisRef(cls) => + // access to top-level objects + val isPackage = cls.is(Flags.Package) + if (!isPackage) assert( + state.currentClass.derivesFrom(cls), + state.currentClass.show + " not derived from " + cls.show + ) // in current class hierachy + + if ( + !isPackage && + !state.inited.contains(field) && + !field.hasAnnotation(defn.ScalaStaticAnnot) && + !field.is(Flags.Lazy) + ) { + traceIndented("initialized: " + state.inited) + + // should issue error, use warning so that it will continue compile subprojects + theCtx.warning( + "Access non-initialized field " + eff.source.show + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + } + + case Warm(cls) => + // all fields of warm values are initialized + + case Dependent(cls, bindings) => + // all fields of warm values are initialized + + case Cold(cls, _) => + theCtx.warning( + "Access field " + eff.source.show + " on a cold value" + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + + case Fun(pots, effs) => + throw new Exception("Unexpected effect " + eff.show) + + case pot => + val state2 = state.withVisited(eff) + val pots = expand(pot) + pots.foreach { pot => check(FieldAccess(pot, field)(eff.source))(state2) } + } + + case MethodCall(pot, sym, virtual) => + pot match { + case ThisRef(cls) => + assert( + state.currentClass.derivesFrom(cls), + state.currentClass.show + " not derived from " + cls.show + ) // in current class hierachy + // overriding resolution + val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym + if (targetSym.exists) { // tests/init/override17.scala + val effs = theEnv.effectsOf(targetSym) + val state2 = state.withVisited(eff) + effs.foreach { check(_)(state2) } + } + else { + traceIndented("!!!sym does not exist: " + pot.show) + } + + case Warm(cls) => + // overriding resolution + val targetSym = if (virtual) resolveVirtual(cls, sym) else sym + val effs = theEnv.effectsOf(targetSym) + val state2 = state.withVisited(eff) + effs.foreach { eff => + val effs = substitute(eff, Map.empty, Some(cls -> pot)) + effs.foreach { eff2 => check(eff2)(state2) } + } + + case Dependent(cls, bindings) => + // overriding resolution + val targetSym = if (virtual) resolveVirtual(cls, sym) else sym + val effs = theEnv.effectsOf(targetSym) + val state2 = state.withVisited(eff) + effs.foreach { eff => + val effs = substitute(eff, bindings, Some(cls -> pot)) + effs.foreach { eff2 => check(eff2)(state2) } + } + + case Cold(cls, _) => + theCtx.warning( + "Call method " + eff.source.show + " on a cold value" + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + + case Fun(pots, effs) => + // TODO: assertion might be false, due to SAM + if (sym.name.toString == "apply") { + val state2 = state.withVisited(eff) + effs.foreach { check(_)(state2) } + pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + } + // curried, tupled, toString are harmless + + case pot => + val state2 = state.withVisited(eff) + val pots = expand(pot) + pots.foreach { pot => + check(MethodCall(pot, sym, virtual)(eff.source))(state2) + } + } + + case DependentCall(pot, sym, bindings) => + assert(sym.isConstructor, "only dependent call of contructor supported") + val state2 = state.withVisited(eff) + pot match { + // `Cold(cls).init(args)`: encoding of new expressions + case Cold(cls, _) => + val effs = theEnv.effectsOf(sym) + if (sym.isPrimaryConstructor) { + val bindings2 = Summarization.dependentParamBindings(sym, bindings) + val potDep = Dependent(cls, bindings2)(eff.source) + effs.foreach { eff => + val effs = substitute(eff, bindings, Some(cls -> potDep)) + effs.foreach { eff2 => check(eff2)(state2) } + } + } + else { + val effs = theEnv.effectsOf(sym) + val potDeps = expand(DependentReturn(pot, sym, bindings)(eff.source)) + assert(potDeps.size == 1, "expect size = 1, found " + potDeps.size) + effs.foreach { eff => + val effs = substitute(eff, bindings, Some(cls -> potDeps.head)) + effs.foreach { eff2 => check(eff2)(state2) } + } + } + + case ThisRef(cls) => + assert( + state.currentClass.derivesFrom(cls), + state.currentClass.show + " not derived from " + cls.show + ) // in current class hierachy + if (sym.exists) { // tests/init/override17.scala + val effs = theEnv.effectsOf(sym) + val state2 = state.withVisited(eff) + effs.foreach { eff => + val effs = substitute(eff, bindings, None) + effs.foreach { eff2 => check(eff2)(state2) } + } + } + else { + traceIndented("!!!sym does not exist: " + pot.show) + } + + case Warm(cls) => + val effs = theEnv.effectsOf(sym) + val state2 = state.withVisited(eff) + effs.foreach { eff => + val effs = substitute(eff, bindings, Some(cls -> pot)) + effs.foreach { eff2 => check(eff2)(state2) } + } + + case Dependent(cls, bindings1) => + val effs = theEnv.effectsOf(sym) + val state2 = state.withVisited(eff) + effs.foreach { eff => + val effs = substitute(eff, bindings, Some(cls -> pot)) + effs.foreach { eff2 => check(eff2)(state2) } + } + + case Fun(pots, effs) => + throw new Exception("Why I'm reached? " + pot.show) + + case pot => + val state2 = state.withVisited(eff) + val pots = expand(pot) + pots.foreach { pot => + check(DependentCall(pot, sym, bindings)(eff.source))(state2) + } + + } + } + } + + private def expand(pot: Potential)(implicit state: State): Potentials = + trace("expand " + pot.show, pots => Potentials.show(pots.asInstanceOf[Potentials])) { pot match { + case MethodReturn(pot1, sym, virtual) => + pot1 match { + case ThisRef(cls) => + assert( + state.currentClass.derivesFrom(cls), + "current class: " + state.currentClass.show + ", tp.symbol = " + cls + ) + val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym + if (targetSym.exists) { + theEnv.potentialsOf(targetSym) + } + else { + traceIndented("!!!sym does not exist: " + pot.show) + Set.empty + } + + case Fun(pots, effs) => + val name = sym.name.toString + if (name == "apply") pots + else if (name == "tupled") Set(pot1) + else if (name == "curried") { + val arity = defn.functionArity(sym.info.finalResultType) + (1 until arity).foldLeft(Set(pot1)) { (acc, i) => Set(Fun(acc, Effects.empty)(pot1.source)) } + } + else Potentials.empty + + case Warm(cls) => + val targetSym = if (virtual) resolveVirtual(cls, sym) else sym + val pots = theEnv.potentialsOf(targetSym) + pots.flatMap { pot2 => substitute(pot2, Map.empty, Some(cls -> pot1)) } + + case Dependent(cls, bindings) => + val targetSym = if (virtual) resolveVirtual(cls, sym) else sym + val pots = theEnv.potentialsOf(targetSym) + pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + + case Cold(cls, _) => + Potentials.empty // error already reported, ignore + + case _ => + val (pots, effs) = expand(pot1).select(sym, pot.source, virtual) + effs.foreach(check(_)) + pots + } + + case FieldReturn(pot1, sym) => + pot1 match { + case ThisRef(cls) => + // access to top-level objects + val isPackage = cls.is(Flags.Package) + if (!isPackage) assert( + state.currentClass.derivesFrom(cls), + "current class: " + state.currentClass.show + ", symbol = " + cls + ) + if (isPackage) Set.empty + else theEnv.potentialsOf(sym).map(devar(_, _ => None)).collect { + case Some(pot) => pot + } + + case Fun(pots, effs) => + throw new Exception("Unexpected code reached") + + case Warm(cls) => + def toCold(sym: Symbol): Potential = Cold(sym.info.classSymbol.asClass, definite = false)(pot.source) + val pots = theEnv.potentialsOf(sym).map(devar(_, sym => Some(toCold(sym)))).collect { + case Some(pot) => pot + } + pots.flatMap { pot2 => substitute(pot2, Map.empty, Some(cls -> pot1)) } + + case Dependent(cls, bindings) => + val pots = theEnv.potentialsOf(sym) + pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + + case Cold(cls, _) => + Potentials.empty // error already reported, ignore + + case _ => + val (pots, effs) = expand(pot1).select(sym, pot.source) + effs.foreach(check(_)) + pots + } + + case DependentReturn(pot1, sym, bindings) => + pot1 match { + case ThisRef(cls) => + // access to top-level objects + val isPackage = cls.is(Flags.Package) + if (!isPackage) assert( + state.currentClass.derivesFrom(cls), + "current class: " + state.currentClass.show + ", symbol = " + cls + ) + if (isPackage) Set.empty + else { + val pots = theEnv.potentialsOf(sym) + pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + } + + case Fun(pots, effs) => + throw new Exception("Unexpected code reached") + + case Warm(cls) => + val pots = theEnv.potentialsOf(sym) + pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + + case Dependent(cls, bindings1) => + val pots = theEnv.potentialsOf(sym) + pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + + case Cold(cls, _) => + if (sym.isPrimaryConstructor) { + val bindings2 = Summarization.dependentParamBindings(sym, bindings) + Potentials.empty + Dependent(cls, bindings2)(pot.source) + } + else if (sym.isConstructor) { + val pots = theEnv.potentialsOf(sym) + assert(pots.size == 1, "pots = " + Potentials.show(pots)) + substitute(pots.head, bindings, None) + } + else { + Potentials.empty // error already reported, ignore + } + + case _ => + val (pots, effs) = expand(pot1).select(sym, pot.source, bindings = bindings) + effs.foreach(check(_)) + pots + } + + case Var(sym) => + assert(sym.owner.isPrimaryConstructor, "sym = " + sym.show) + assert( + state.currentClass.derivesFrom(sym.owner.owner), + "current = " + state.currentClass.show + ", owner = " + sym.owner.owner.show + ) + Potentials.empty + + case _: ThisRef | _: Fun | _: Warm | _: Cold | _: Dependent => + Set(pot) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index e2a746835345..fbabeaae18ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -70,7 +70,7 @@ object Potentials { def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true, bindings: Map[Symbol, Potentials] = Map.empty)(implicit ctx: Context): Summary = ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => - if (pot.size > 3) + if (pot.size > 1) (pots, effs + Leak(pot)(source)) else if (symbol.is(Flags.Method)) ( diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 463b5a23cede..e828e2558b32 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -141,11 +141,11 @@ object Summarization { // TODO: bindings analyze(expansion) - case tree @ ValDef(name, tpt, _) => - if (tree.symbol.is(Flags.Lazy)) Summary.empty + case vdef : ValDef => + if (vdef.symbol.is(Flags.Lazy)) Summary.empty else { - val (pots, effs) = analyze(tree.rhs) - (Potentials.empty, pots.leak(expr) ++ effs) + val (pots, effs) = analyze(vdef.rhs) + (Potentials.empty, pots.leak(vdef) ++ effs) } case Thicket(List()) => From 0494ac1e8b5740aaaf848641cd0b11fa8230c4a1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 15:58:28 +0100 Subject: [PATCH 011/112] WIP - implementation linearization --- .../tools/dotc/transform/init/Checking.scala | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 0895f4736513..0d3beada49ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -107,24 +107,43 @@ object Checking { effs.foreach { check(_) } } + def checkCtor(ctor: Symbol, tp: Type, source: Tree)(implicit ctx: Context): Unit = { + val cls = ctor.owner + val classDef = cls.defTree + if (!classDef.isEmpty) { + val outer = tp.typeConstructor match { + case tref: TypeRef => Summarization.analyze(tref.prefix, source) + } + if (ctor.isPrimaryConstructor) checkClassBody(classDef, outer) + else checkSecondaryConstructor(ctor, outer) + } + else if (!cls.is(Flags.EffectivelyOpenFlags)) + ctx.warning("Inheriting non-open class may cause initialization errors", ref.sourcePos) + } + tpl.parents.foreach { - case Block(stats, parent) => + case tree @ Block(stats, parent) => val (ctor, _, argss) = decomposeCall(parent) checkStats(stats) checkStats(argss.flatten) + checkCtor(ctor.symbol, parent.tpe, tree) - case Apply(Block(stats, parent), args) => + case tree @ Apply(Block(stats, parent), args) => val (ctor, _, argss) = decomposeCall(parent) checkStats(stats) checkStats(args) checkStats(argss.flatten) + checkCtor(ctor.symbol, tree.tpe, tree) case parent : Apply => val (ctor, _, argss) = decomposeCall(parent) checkStats(argss.flatten) + checkCtor(ctor.symbol, parent.tpe, parent) - case ref @ (_ : Ident | _ : Select) => - + case ref => + val cls = ref.symbol.asClass + if (!state.parentInited.contains(cls)) + checkCtor(cls.primaryConstructor, ref.tpe, ref) } // check class body From cbc4a315ef9b056c0c87ce65431be12465bc556d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 16:10:32 +0100 Subject: [PATCH 012/112] Handle lazy fields --- .../src/dotty/tools/dotc/transform/init/Checking.scala | 10 ++++++---- compiler/src/dotty/tools/dotc/transform/init/Env.scala | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 0d3beada49ec..5f30eb86855f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -86,10 +86,12 @@ object Checking { case vdef : ValDef => val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) - theEnv.cachePotentialsFor(vdef.symbol, pots) - traceIndented(vdef.symbol.show + " initialized") - effs.foreach { check(_) } - state.inited += vdef.symbol + theEnv.cacheFor(vdef.symbol, (pots, effs)) + if (!vdef.symbol.is(Flags.Lazy)) { + traceIndented(vdef.symbol.show + " initialized") + effs.foreach { check(_) } + state.inited += vdef.symbol + } case tree => val (_, effs) = Summarization.analyze(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index fb9925540576..16d8f192dae4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -21,8 +21,8 @@ case class Env(ctx: Context, summaryCache: Map[Symbol, Summary]) { def withCtx(newCtx: Context): Env = this.copy(ctx = newCtx) - def cachePotentialsFor(symbol: Symbol, pots: Potentials): Unit = { - summaryCache(symbol) = (pots, Effects.empty) + def cacheFor(symbol: Symbol, summary: Summary): Unit = { + summaryCache(symbol) = summary } /** Summary of a method or field */ From 820c67b9b002b60378d521c3b554d1c40d455cd2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 16:20:22 +0100 Subject: [PATCH 013/112] Support traceOp --- .../dotty/tools/dotc/transform/init/Checking.scala | 13 +++++++------ .../src/dotty/tools/dotc/transform/init/Util.scala | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 5f30eb86855f..b0c134386392 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -12,6 +12,7 @@ import Symbols._ import Constants.Constant import Types._ import util.NoSourcePosition +import config.Printers.init import Effects._, Potentials._, Summary._, Util._ @@ -69,18 +70,18 @@ object Checking { * However, summarization can be done lazily on-demand to improve * performance. */ - def checkClassBody(cdef: TypeDef, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show) { + def checkClassBody(cdef: TypeDef, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show, init) { val tpl = cdef.rhs.asInstanceOf[Template] // mark current class as initialized state.parentInited += cdef.symbol.asClass -> outer - def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show) { + def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show, init) { tree match { case Apply(sel @ Select(_: This, _), _) if sel.symbol.isConstructor => // ctor call inside 2nd ctor val tree = sel.symbol.defTree - traceOp("check ctor: " + sel.symbol.show) { + traceOp("check ctor: " + sel.symbol.show, init) { if (!tree.isEmpty) doCheck(tree.asInstanceOf[DefDef].rhs)(ctx.withOwner(tree.symbol)) } @@ -152,11 +153,11 @@ object Checking { tpl.body.foreach { checkClassBodyStat(_) } } - def checkSecondaryConstructor(ctor: Symbol, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + ctor.show) { + def checkSecondaryConstructor(ctor: Symbol, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) { val Block(ctorCall :: stats, expr) = ctor.defTree val ctorCallSym = ctorCall.symbol - traceOp("check ctor: " + sel.symbol.show) { + traceOp("check ctor: " + sel.symbol.show, init) { if (ctorCallSym.isPrimaryConstructor) checkClassBody(ctorCallSym.owner.defTree.asInstanceOf[TypeDef], outer) else @@ -176,7 +177,7 @@ object Checking { else sym.matchingMember(thisClass.typeRef) private def check(eff: Effect)(implicit state: State): Unit = - if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show) { + if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show, init) { eff match { case Leak(pot) => pot match { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 7c41f1389565..61e30102c4ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -9,4 +9,10 @@ import config.Printers.Printer object Util { def traceIndented(msg: String, printer: Printer)(implicit ctx: Context): Unit = printer.println(s"${ctx.base.indentTab * ctx.base.indent} $msg") + + def traceOp(msg: String, printer: Printer)(op: => Unit)(implicit ctx: Context): Unit = { + traceIndented(s"==> ${msg}", printer) + op + traceIndented(s"<== ${msg}", printer) + } } \ No newline at end of file From 6f11bf60a2ceaac873f7ca4d2eea28be9cc42cb1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 16:30:27 +0100 Subject: [PATCH 014/112] Summarize bindings in inlined --- .../src/dotty/tools/dotc/transform/init/Summarization.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index e828e2558b32..57ef067126c0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -138,8 +138,8 @@ object Summarization { (Potentials.empty, effsAll) case Inlined(call, bindings, expansion) => - // TODO: bindings - analyze(expansion) + val effs = bindings.foldLeft(Effects.empty) { (acc, mdef) => acc ++ analyze(mdef)._2 } + analyze(expansion).withEffs(effs) case vdef : ValDef => if (vdef.symbol.is(Flags.Lazy)) Summary.empty From c58a96754de5d68ba603bf2b975a62dbcda00f77 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 11 Dec 2019 17:16:55 +0100 Subject: [PATCH 015/112] WIP - summarization of new --- .../tools/dotc/transform/init/Summarization.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 57ef067126c0..98a3d33fc4c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -69,7 +69,16 @@ object Summarization { Summary.empty case New(tpt) => - ??? + tpt.tpe.typeConstructor match { + case tref: TypeRef => + val cls = tref.classSymbol + if (tref.prefix == NoPrefix) Warm(cls, Map.empty) + else { + val (pots, effs) = analyze(tref.prefix, expr) + if (pots.isEmpty) Summary.empty.withEffs(effs) + else Warm(cls, Map.empty) + } + } case Typed(expr, tpt) => analyze(expr) From 073eec2d944285a1357e3c3e35bd84c97da944f0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 12 Dec 2019 07:55:54 +0100 Subject: [PATCH 016/112] WIP - introduce ClassSummary --- .../dotty/tools/dotc/transform/init/Env.scala | 14 ++++------ .../dotc/transform/init/Potentials.scala | 4 +-- .../dotc/transform/init/Summarization.scala | 1 + .../tools/dotc/transform/init/Summary.scala | 27 +++++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index 16d8f192dae4..b91153a3f387 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -10,25 +10,21 @@ import Symbols._ import reporting.trace import config.Printers.init -import scala.collection.mutable.Map +import scala.collection.mutable import Effects._, Potentials._, Summary._ implicit def theCtx(implicit env: Env): Context = env.ctx -case class Env(ctx: Context, summaryCache: Map[Symbol, Summary]) { +case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary]) { private implicit def self: Env = this def withCtx(newCtx: Context): Env = this.copy(ctx = newCtx) - def cacheFor(symbol: Symbol, summary: Summary): Unit = { - summaryCache(symbol) = summary - } - /** Summary of a method or field */ - def summaryOf(symbol: Symbol): Summary = - if (summaryCache.contains(symbol)) summaryCache(symbol) - else trace("summary for " + symbol.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + def summaryOf(cls: ClassSymbol): ClassSummary = + if (summaryCache.contains(cls)) summaryCache(cls) + else trace("summary for " + cls.show, init, s => Summary.show(s.asInstanceOf[ClassSummary])) { val summary = if (symbol.isConstructor) Summarization.analyzeConstructor(symbol) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index fbabeaae18ec..4b617031fe83 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -35,9 +35,9 @@ object Potentials { * reference to objects under initialization. * * @param cls The concrete class of the object - * @param outers The potentials for the immdiate outer `this`. One entry for each class in the inheritance hierarchy. + * @param outer The potentials for the immdiate outer `this` */ - case class Warm(cls: ClassSymbol, outers: Map[ClassSymbol, Potentials])(val source: Tree) extends Potential { + case class Warm(cls: ClassSymbol, outer: Potentials)(val source: Tree) extends Potential { def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 98a3d33fc4c4..9daa6862b6ce 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -72,6 +72,7 @@ object Summarization { tpt.tpe.typeConstructor match { case tref: TypeRef => val cls = tref.classSymbol + // local class may capture, thus we need to track it if (tref.prefix == NoPrefix) Warm(cls, Map.empty) else { val (pots, effs) = analyze(tref.prefix, expr) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index edeb8ebc20dd..9bd498ec4c9f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -2,8 +2,12 @@ package dotty.tools.dotc package transform package init +import scala.collection.mutable + import core._ import Contexts.Context +import reporting.trace +import config.Printers.init import Potentials._, Effects._ @@ -11,6 +15,29 @@ object Summary { type Summary = (Potentials, Effects) val empty: Summary = (Potentials.empty, Effects.empty) + case class ClassSummary( + outer: Potentials, + cls: ClassSymbol, + parents: List[Potentials] + ) { + private val summaryCache: mutable.Map[ClassSymbol, ClassSummary] = mutable.Map.empty + + def summaryOf(member: Symbol): Summary = + if (summaryCache.contains(member)) summaryCache(clmembers) + else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val summary = + if (symbol.isConstructor) + Summarization.analyzeConstructor(symbol) // TODO: asSeenFrom + else if (symbol.is(Flags.Method)) + Summarization.analyzeMethod(symbol) // TODO: asSeenFrom + else // field + Summarization.analyzeField(symbol) // TODO: asSeenFrom + + summaryCache(symbol) = summary + summary + } + } + def show(summary: Summary)(implicit ctx: Context): String = { val pots = Potentials.show(summary._1) val effs = Effects.show(summary._2) From 594c1bf594fc62d6f2609a918e6ee04c29fb35a2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 12 Dec 2019 14:08:55 +0100 Subject: [PATCH 017/112] Add show to ClassSummary --- .../src/dotty/tools/dotc/transform/init/Env.scala | 15 ++------------- .../tools/dotc/transform/init/Potentials.scala | 2 +- .../tools/dotc/transform/init/Summarization.scala | 4 ++-- .../dotty/tools/dotc/transform/init/Summary.scala | 12 ++++++++++-- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index b91153a3f387..a619a4d8ab5c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -24,20 +24,9 @@ case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary /** Summary of a method or field */ def summaryOf(cls: ClassSymbol): ClassSummary = if (summaryCache.contains(cls)) summaryCache(cls) - else trace("summary for " + cls.show, init, s => Summary.show(s.asInstanceOf[ClassSummary])) { - val summary = - if (symbol.isConstructor) - Summarization.analyzeConstructor(symbol) - else if (symbol.is(Flags.Method)) - Summarization.analyzeMethod(symbol) - else // field - Summarization.analyzeField(symbol) - + else trace("summary for " + cls.show, init, s => s.asInstanceOf[ClassSummary].show) { + val summary = Summarization.classSummary(cls) summaryCache(symbol) = summary summary } - - def effectsOf(symbol: Symbol): Effects = summaryOf(symbol)._2 - - def potentialsOf(symbol: Symbol): Potentials = summaryOf(symbol)._1 } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 4b617031fe83..01b4b27fb962 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -66,7 +66,7 @@ object Potentials { "Fun[pots = " + potentials.map(_.show).mkString(";") + ", effs = " + effects.map(_.show).mkString(";") + "]" } - // --------- operations on potentials --------- + // ------------------ operations on potentials ------------------ def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true, bindings: Map[Symbol, Potentials] = Map.empty)(implicit ctx: Context): Summary = ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 9daa6862b6ce..c9016307371b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -73,11 +73,11 @@ object Summarization { case tref: TypeRef => val cls = tref.classSymbol // local class may capture, thus we need to track it - if (tref.prefix == NoPrefix) Warm(cls, Map.empty) + if (tref.prefix == NoPrefix) Warm(cls, Potentials.empty) else { val (pots, effs) = analyze(tref.prefix, expr) if (pots.isEmpty) Summary.empty.withEffs(effs) - else Warm(cls, Map.empty) + else Warm(cls, pots) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 9bd498ec4c9f..1be5265e37f8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -18,11 +18,11 @@ object Summary { case class ClassSummary( outer: Potentials, cls: ClassSymbol, - parents: List[Potentials] + parentOuter: Map[ClassSymbol, Potentials] ) { private val summaryCache: mutable.Map[ClassSymbol, ClassSummary] = mutable.Map.empty - def summaryOf(member: Symbol): Summary = + def summaryOf(member: Symbol)(implicit ctx: Context): Summary = if (summaryCache.contains(member)) summaryCache(clmembers) else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary = @@ -36,6 +36,14 @@ object Summary { summaryCache(symbol) = summary summary } + + def effectsOf(member: Symbol)(implicit ctx: Context): Effects = summaryOf(symbol)._2 + + def potentialsOf(member: Symbol)(implicit ctx: Context): Potentials = summaryOf(symbol)._1 + + def show(implicit ctx: Context): String = + "ClassSummary(" + cls.name.show + ", outer = " + Potentials.show(outer) + + "parents = {" + parentOuter.map { (k, v) => k.show + "->" + "[" Potentials.show(v) + "]" } + "}" } def show(summary: Summary)(implicit ctx: Context): String = { From 94267d73c4990ea1481ff0c16726ee85be9f53ac Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 12 Dec 2019 15:03:58 +0100 Subject: [PATCH 018/112] Add code to summarize class --- .../dotc/transform/init/Summarization.scala | 26 +++++++++++++++++ .../tools/dotc/transform/init/Summary.scala | 29 ++++++++++++------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index c9016307371b..cbcc945b70e8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -152,6 +152,7 @@ object Summarization { analyze(expansion).withEffs(effs) case vdef : ValDef => + // Local lazy vals be hot too? if (vdef.symbol.is(Flags.Lazy)) Summary.empty else { val (pots, effs) = analyze(vdef.rhs) @@ -223,4 +224,29 @@ object Summarization { analyze(ddef.rhs)(ctx.withOwner(ctor)) } } + + def classSummary(cls: ClassSymbol)(implicit ctx: Context): ClassSummary = + if (cls.defTree.isEmpty) + cls.info match { + case cinfo: ClassInfo => + val parentOuter = cinfo.classParents.map { + case parentTp: TypeRef => + val source = TypeTree(parentTp).withSourcePos(cls.sourcePos) + parentTp.classSymbol -> analyze(parentTp.prefix, source) + } + + ClassSummary(Potentials.empty, cls, parentOuter) + } + else { + val tpl = cls.defTree.asInstanceOf[TypeDef] + val parents = tpl.rhs.asInstanceOf[Template].parents + + val parentOuter = parents.map { parent => + val tref = parent.tpe.typeConstructor.asInstanceOf[TypeRef] + parent.classSymbol -> analyze(tref.prefix, parent) + } + + ClassSummary(Potentials.empty, cls, parentOuter) + } + } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 1be5265e37f8..3e3f99c9e76b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -15,34 +15,41 @@ object Summary { type Summary = (Potentials, Effects) val empty: Summary = (Potentials.empty, Effects.empty) - case class ClassSummary( - outer: Potentials, - cls: ClassSymbol, - parentOuter: Map[ClassSymbol, Potentials] - ) { - private val summaryCache: mutable.Map[ClassSymbol, ClassSummary] = mutable.Map.empty + case class ClassSummary(currentClass: ClassSymbol, parentOuter: Map[ClassSymbol, Potentials]) { + private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty def summaryOf(member: Symbol)(implicit ctx: Context): Summary = if (summaryCache.contains(member)) summaryCache(clmembers) else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary = if (symbol.isConstructor) - Summarization.analyzeConstructor(symbol) // TODO: asSeenFrom + Summarization.analyzeConstructor(symbol) else if (symbol.is(Flags.Method)) - Summarization.analyzeMethod(symbol) // TODO: asSeenFrom + Summarization.analyzeMethod(symbol) else // field - Summarization.analyzeField(symbol) // TODO: asSeenFrom + Summarization.analyzeField(symbol) summaryCache(symbol) = summary summary } def effectsOf(member: Symbol)(implicit ctx: Context): Effects = summaryOf(symbol)._2 - def potentialsOf(member: Symbol)(implicit ctx: Context): Potentials = summaryOf(symbol)._1 + } + + case class ObjectPart( + thisValue: Warm | ThisRef, // the potential for `this`, it can be Warm or ThisRef + currentClass: ClassSymbol, // current class + currentOuter: Potentials, // the immediate outer for current class, empty for local and top-level classes + parentOuter: Map[ClassSymbol, Potentials] // outers for direct parents + ) { + private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty + + /** Summary of a member field or method, with `this` and outers substituted */ + def summaryOf(member: Symbol)(implicit ctx: Context): Summary = ??? def show(implicit ctx: Context): String = - "ClassSummary(" + cls.name.show + ", outer = " + Potentials.show(outer) + + "ClassSummary(" + currentClass.name.show + ", outer = " + Potentials.show(outer) + "parents = {" + parentOuter.map { (k, v) => k.show + "->" + "[" Potentials.show(v) + "]" } + "}" } From b94ce0085039bb8daf52121669a6660318a2c953 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 11:22:00 +0100 Subject: [PATCH 019/112] Fix typing errors --- .../tools/dotc/transform/init/Checking.scala | 7 ---- .../dotty/tools/dotc/transform/init/Env.scala | 2 +- .../dotc/transform/init/Summarization.scala | 27 +++++++------- .../tools/dotc/transform/init/Summary.scala | 35 +++++++++++++------ 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index b0c134386392..617d8c71b66d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -78,13 +78,6 @@ object Checking { def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show, init) { tree match { - case Apply(sel @ Select(_: This, _), _) if sel.symbol.isConstructor => - // ctor call inside 2nd ctor - val tree = sel.symbol.defTree - traceOp("check ctor: " + sel.symbol.show, init) { - if (!tree.isEmpty) doCheck(tree.asInstanceOf[DefDef].rhs)(ctx.withOwner(tree.symbol)) - } - case vdef : ValDef => val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) theEnv.cacheFor(vdef.symbol, (pots, effs)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index a619a4d8ab5c..e8a8a1f0883d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -26,7 +26,7 @@ case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary if (summaryCache.contains(cls)) summaryCache(cls) else trace("summary for " + cls.show, init, s => s.asInstanceOf[ClassSummary].show) { val summary = Summarization.classSummary(cls) - summaryCache(symbol) = summary + summaryCache(cls) = summary summary } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index cbcc945b70e8..ddf4e62b203a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -71,13 +71,13 @@ object Summarization { case New(tpt) => tpt.tpe.typeConstructor match { case tref: TypeRef => - val cls = tref.classSymbol + val cls = tref.classSymbol.asClass // local class may capture, thus we need to track it - if (tref.prefix == NoPrefix) Warm(cls, Potentials.empty) + if (tref.prefix == NoPrefix) Summary.empty + Warm(cls, Potentials.empty)(expr) else { val (pots, effs) = analyze(tref.prefix, expr) if (pots.isEmpty) Summary.empty.withEffs(effs) - else Warm(cls, pots) + else Summary.empty + Warm(cls, pots)(expr) } } @@ -229,24 +229,27 @@ object Summarization { if (cls.defTree.isEmpty) cls.info match { case cinfo: ClassInfo => - val parentOuter = cinfo.classParents.map { + val parentOuter: List[(ClassSymbol, Potentials)] = cinfo.classParents.map { case parentTp: TypeRef => - val source = TypeTree(parentTp).withSourcePos(cls.sourcePos) - parentTp.classSymbol -> analyze(parentTp.prefix, source) - } - - ClassSummary(Potentials.empty, cls, parentOuter) + val source = { + implicit val ctx2: Context = ctx.withSource(cls.source) + TypeTree(parentTp).withSpan(cls.span) + } + parentTp.classSymbol.asClass -> analyze(parentTp.prefix, source)._1 + }) + + ClassSummary(cls, parentOuter.toMap) } else { val tpl = cls.defTree.asInstanceOf[TypeDef] val parents = tpl.rhs.asInstanceOf[Template].parents - val parentOuter = parents.map { parent => + val parentOuter: List[(ClassSymbol, Potentials)] = parents.map { parent => val tref = parent.tpe.typeConstructor.asInstanceOf[TypeRef] - parent.classSymbol -> analyze(tref.prefix, parent) + parent.symbol.asClass -> analyze(tref.prefix, parent)._1 } - ClassSummary(Potentials.empty, cls, parentOuter) + ClassSummary(cls, parentOuter.toMap) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 3e3f99c9e76b..3449c5023224 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -6,6 +6,7 @@ import scala.collection.mutable import core._ import Contexts.Context +import Symbols._ import reporting.trace import config.Printers.init @@ -15,28 +16,40 @@ object Summary { type Summary = (Potentials, Effects) val empty: Summary = (Potentials.empty, Effects.empty) + /** Summary of class. + * + * It makes ObjectPart construction easier with already established raw outer for parents. + */ case class ClassSummary(currentClass: ClassSymbol, parentOuter: Map[ClassSymbol, Potentials]) { private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty def summaryOf(member: Symbol)(implicit ctx: Context): Summary = - if (summaryCache.contains(member)) summaryCache(clmembers) + if (summaryCache.contains(member)) summaryCache(member) else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary = - if (symbol.isConstructor) - Summarization.analyzeConstructor(symbol) - else if (symbol.is(Flags.Method)) - Summarization.analyzeMethod(symbol) + if (member.isConstructor) + Summarization.analyzeConstructor(member) + else if (member.is(Flags.Method)) + Summarization.analyzeMethod(member) else // field - Summarization.analyzeField(symbol) + Summarization.analyzeField(member) - summaryCache(symbol) = summary + summaryCache(member) = summary summary } - def effectsOf(member: Symbol)(implicit ctx: Context): Effects = summaryOf(symbol)._2 - def potentialsOf(member: Symbol)(implicit ctx: Context): Potentials = summaryOf(symbol)._1 + def effectsOf(member: Symbol)(implicit ctx: Context): Effects = summaryOf(member)._2 + def potentialsOf(member: Symbol)(implicit ctx: Context): Potentials = summaryOf(member)._1 + + def show(implicit ctx: Context): String = + "ObjectPart(" + currentClass.name.show + + "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" Potentials.show(v) + "]" } + "}" } + /** Part of object. + * + * It makes prefix and outer substitution easier in effect checking. + */ case class ObjectPart( thisValue: Warm | ThisRef, // the potential for `this`, it can be Warm or ThisRef currentClass: ClassSymbol, // current class @@ -49,8 +62,8 @@ object Summary { def summaryOf(member: Symbol)(implicit ctx: Context): Summary = ??? def show(implicit ctx: Context): String = - "ClassSummary(" + currentClass.name.show + ", outer = " + Potentials.show(outer) + - "parents = {" + parentOuter.map { (k, v) => k.show + "->" + "[" Potentials.show(v) + "]" } + "}" + "ObjectPart(this = " + thisValue.show + "," + currentClass.name.show + ", outer = " + Potentials.show(currentOuter) + + "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" } def show(summary: Summary)(implicit ctx: Context): String = { From 67e7165fea68b42570b05ea12d581e308c4e365c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 11:24:53 +0100 Subject: [PATCH 020/112] Fix Summary and Summarization errors --- .../src/dotty/tools/dotc/transform/init/Summarization.scala | 4 ++-- compiler/src/dotty/tools/dotc/transform/init/Summary.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index ddf4e62b203a..10e7c0c86f4d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -232,11 +232,11 @@ object Summarization { val parentOuter: List[(ClassSymbol, Potentials)] = cinfo.classParents.map { case parentTp: TypeRef => val source = { - implicit val ctx2: Context = ctx.withSource(cls.source) + implicit val ctx2: Context = ctx.withSource(cls.source(ctx)) TypeTree(parentTp).withSpan(cls.span) } parentTp.classSymbol.asClass -> analyze(parentTp.prefix, source)._1 - }) + } ClassSummary(cls, parentOuter.toMap) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 3449c5023224..c5e01296787f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -43,7 +43,7 @@ object Summary { def show(implicit ctx: Context): String = "ObjectPart(" + currentClass.name.show + - "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" Potentials.show(v) + "]" } + "}" + "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" } /** Part of object. From e5e1c68d80cf5a1165455e44cd8561efeb488e53 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 11:53:03 +0100 Subject: [PATCH 021/112] Fix more typing errors in Checking --- .../tools/dotc/transform/init/Checking.scala | 45 ++++++++++--------- .../tools/dotc/transform/init/Summary.scala | 5 +++ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 617d8c71b66d..c5047f9e97d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -29,11 +29,11 @@ object Checking { * */ case class State( - visited: mutable.Set[Effect], - path: Vector[Tree], - currentClass: ClassSymbol, - inited: mutable.Set[Symbol], - parentInited: mutable.Map[ClassSymbol, Potentials], + visited: mutable.Set[Effect], // effects that have been expanded + path: Vector[Tree], // the path that leads to the current effect + thisClass: ClassSymbol, // the concrete class of `this` + fieldsInited: mutable.Set[Symbol], + parentsInited: mutable.Set[ClassSymbol], env: Env ) { def withVisited(eff: Effect)(implicit ctx: Context): State = { @@ -71,20 +71,21 @@ object Checking { * performance. */ def checkClassBody(cdef: TypeDef, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show, init) { + val cls = cdef.symbol.asClass val tpl = cdef.rhs.asInstanceOf[Template] - // mark current class as initialized - state.parentInited += cdef.symbol.asClass -> outer + // mark current class as initialized, required for linearization + state.parentsInited += cls def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show, init) { tree match { case vdef : ValDef => val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) - theEnv.cacheFor(vdef.symbol, (pots, effs)) + theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs)) if (!vdef.symbol.is(Flags.Lazy)) { - traceIndented(vdef.symbol.show + " initialized") + traceIndented(vdef.symbol.show + " initialized", init) effs.foreach { check(_) } - state.inited += vdef.symbol + state.fieldsInited += vdef.symbol } case tree => @@ -107,14 +108,18 @@ object Checking { val cls = ctor.owner val classDef = cls.defTree if (!classDef.isEmpty) { - val outer = tp.typeConstructor match { + val (pots, effs) = tp.typeConstructor match { case tref: TypeRef => Summarization.analyze(tref.prefix, source) } - if (ctor.isPrimaryConstructor) checkClassBody(classDef, outer) + + // TODO: no need to check, can only be empty + effs.foreach { check(_) } + + if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef], pots) else checkSecondaryConstructor(ctor, outer) } - else if (!cls.is(Flags.EffectivelyOpenFlags)) - ctx.warning("Inheriting non-open class may cause initialization errors", ref.sourcePos) + else if (!cls.isOneOf(Flags.EffectivelyOpenFlags)) + ctx.warning("Inheriting non-open class may cause initialization errors", source.sourcePos) } tpl.parents.foreach { @@ -138,7 +143,7 @@ object Checking { case ref => val cls = ref.symbol.asClass - if (!state.parentInited.contains(cls)) + if (!state.parentsInited.contains(cls)) checkCtor(cls.primaryConstructor, ref.tpe, ref) } @@ -150,15 +155,15 @@ object Checking { val Block(ctorCall :: stats, expr) = ctor.defTree val ctorCallSym = ctorCall.symbol - traceOp("check ctor: " + sel.symbol.show, init) { + traceOp("check ctor: " + ctor.show, init) { if (ctorCallSym.isPrimaryConstructor) checkClassBody(ctorCallSym.owner.defTree.asInstanceOf[TypeDef], outer) else checkSecondaryConstructor(ctorCallSym, outer) } - (stats :+ expr).foreach { - val (_, effs) = Summarization.analyze(tree)(ctx.withOwner(ctor)) + (stats :+ expr).foreach { stat => + val (_, effs) = Summarization.analyze(stat)(theCtx.withOwner(ctor)) effs.foreach { check(_)(state) } } } @@ -225,11 +230,11 @@ object Checking { if ( !isPackage && - !state.inited.contains(field) && + !state.fieldsInited.contains(field) && !field.hasAnnotation(defn.ScalaStaticAnnot) && !field.is(Flags.Lazy) ) { - traceIndented("initialized: " + state.inited) + traceIndented("initialized: " + state.fieldsInited) // should issue error, use warning so that it will continue compile subprojects theCtx.warning( diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index c5e01296787f..ea61a91b18ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -23,6 +23,11 @@ object Summary { case class ClassSummary(currentClass: ClassSymbol, parentOuter: Map[ClassSymbol, Potentials]) { private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty + def cacheFor(member: Symbol, summary: Summary): Unit = { + assert(member.owner == currentClass, "owner = " + member.owner.show + ", current = " + currentClass.show) + summaryCache(member) = summary + } + def summaryOf(member: Symbol)(implicit ctx: Context): Summary = if (summaryCache.contains(member)) summaryCache(member) else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { From dc680dc5dd6d36c2effe9b997d02d2979402dfa0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 13:16:14 +0100 Subject: [PATCH 022/112] Remove obsolete cases in Checking --- .../tools/dotc/transform/init/Checking.scala | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index c5047f9e97d7..b8c6146f2176 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -192,21 +192,13 @@ object Checking { eff.source.sourcePos ) - case Warm(cls) => + case Warm(cls, outer) => theCtx.warning( "Leaking of warm value " + eff.source.show + ". Calling trace:\n" + state.trace, eff.source.sourcePos ) - case Dependent(cls, bindings) => - val state2 = state.withVisited(eff) - bindings.values.flatten.flatMap(pot => - substitute(pot, bindings, None) - ).foreach { pot => - check(Leak(pot)(eff.source))(state2) - } - case Fun(pots, effs) => val state2 = state.withVisited(eff) effs.foreach { check(_)(state2) } @@ -221,13 +213,6 @@ object Checking { case FieldAccess(pot, field) => pot match { case ThisRef(cls) => - // access to top-level objects - val isPackage = cls.is(Flags.Package) - if (!isPackage) assert( - state.currentClass.derivesFrom(cls), - state.currentClass.show + " not derived from " + cls.show - ) // in current class hierachy - if ( !isPackage && !state.fieldsInited.contains(field) && @@ -247,9 +232,6 @@ object Checking { case Warm(cls) => // all fields of warm values are initialized - case Dependent(cls, bindings) => - // all fields of warm values are initialized - case Cold(cls, _) => theCtx.warning( "Access field " + eff.source.show + " on a cold value" + From 5310886366b1ae3c686581476acf8fafe955ed38 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 14:01:52 +0100 Subject: [PATCH 023/112] More fixes --- compiler/src/dotty/tools/dotc/transform/init/Checking.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index b8c6146f2176..83d715ff8395 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -214,12 +214,11 @@ object Checking { pot match { case ThisRef(cls) => if ( - !isPackage && + cls eq state.thisClass && !state.fieldsInited.contains(field) && - !field.hasAnnotation(defn.ScalaStaticAnnot) && !field.is(Flags.Lazy) ) { - traceIndented("initialized: " + state.fieldsInited) + traceIndented("initialized: " + state.fieldsInited, init) // should issue error, use warning so that it will continue compile subprojects theCtx.warning( From 6e6a18155cb8531b840ea48776f158f1617a0dab Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 15:16:10 +0100 Subject: [PATCH 024/112] WIP --- .../tools/dotc/transform/init/Checking.scala | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 83d715ff8395..bb8a7fa120dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -233,7 +233,7 @@ object Checking { case Cold(cls, _) => theCtx.warning( - "Access field " + eff.source.show + " on a cold value" + + "Access field " + eff.source.show + " on a known value under initialization" + ". Calling trace:\n" + state.trace, eff.source.sourcePos ) @@ -250,19 +250,17 @@ object Checking { case MethodCall(pot, sym, virtual) => pot match { case ThisRef(cls) => - assert( - state.currentClass.derivesFrom(cls), - state.currentClass.show + " not derived from " + cls.show - ) // in current class hierachy - // overriding resolution - val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym - if (targetSym.exists) { // tests/init/override17.scala - val effs = theEnv.effectsOf(targetSym) - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } - } - else { - traceIndented("!!!sym does not exist: " + pot.show) + if (cls eq state.thisClass) { + // overriding resolution + val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym + if (targetSym.exists) { // tests/init/override17.scala + val effs = theEnv.effectsOf(targetSym) + val state2 = state.withVisited(eff) + effs.foreach { check(_)(state2) } + } + else { + traceIndented("!!!sym does not exist: " + pot.show) + } } case Warm(cls) => From 994c2408d5c008d4ed084760682749cb7ee26af7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 16:00:16 +0100 Subject: [PATCH 025/112] Add asSeenFrom --- .../tools/dotc/transform/init/Checking.scala | 22 +++++++++++-------- .../tools/dotc/transform/init/Effects.scala | 7 ++++++ .../dotc/transform/init/Potentials.scala | 5 +++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index bb8a7fa120dd..ee3fb9b248ba 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -70,7 +70,7 @@ object Checking { * However, summarization can be done lazily on-demand to improve * performance. */ - def checkClassBody(cdef: TypeDef, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show, init) { + def checkClassBody(cdef: TypeDef)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show, init) { val cls = cdef.symbol.asClass val tpl = cdef.rhs.asInstanceOf[Template] @@ -84,7 +84,7 @@ object Checking { theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs)) if (!vdef.symbol.is(Flags.Lazy)) { traceIndented(vdef.symbol.show + " initialized", init) - effs.foreach { check(_) } + checkEffects(effs) state.fieldsInited += vdef.symbol } @@ -94,6 +94,9 @@ object Checking { } } + def checkEffects(effs: Effects): Unit = + effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty).foreach { check(_) } + // check parent calls : follows linearization ordering // see spec 5.1 about "Template Evaluation". // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#class-linearization @@ -101,7 +104,7 @@ object Checking { def checkStats(stats: List[Tree])(implicit ctx: Context): Unit = stats.foreach { stat => val (_, effs) = Summarization.analyze(stat) - effs.foreach { check(_) } + checkEffects(effs) } def checkCtor(ctor: Symbol, tp: Type, source: Tree)(implicit ctx: Context): Unit = { @@ -113,10 +116,10 @@ object Checking { } // TODO: no need to check, can only be empty - effs.foreach { check(_) } + checkEffects(effs) if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef], pots) - else checkSecondaryConstructor(ctor, outer) + else checkSecondaryConstructor(ctor) } else if (!cls.isOneOf(Flags.EffectivelyOpenFlags)) ctx.warning("Inheriting non-open class may cause initialization errors", source.sourcePos) @@ -151,19 +154,20 @@ object Checking { tpl.body.foreach { checkClassBodyStat(_) } } - def checkSecondaryConstructor(ctor: Symbol, outer: Potentials)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) { + def checkSecondaryConstructor(ctor: Symbol)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) { val Block(ctorCall :: stats, expr) = ctor.defTree - val ctorCallSym = ctorCall.symbol + val cls = ctor.owner traceOp("check ctor: " + ctor.show, init) { if (ctorCallSym.isPrimaryConstructor) - checkClassBody(ctorCallSym.owner.defTree.asInstanceOf[TypeDef], outer) + checkClassBody(cls.defTree.asInstanceOf[TypeDef]) else - checkSecondaryConstructor(ctorCallSym, outer) + checkSecondaryConstructor(ctor) } (stats :+ expr).foreach { stat => val (_, effs) = Summarization.analyze(stat)(theCtx.withOwner(ctor)) + effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty).foreach { check(_) } effs.foreach { check(_)(state) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index d448ce9ebbd3..ff92c386205d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -41,4 +41,11 @@ object Effects { potential.show + "." + method.name.show + "!" + modifier } } + + // ------------------ operations on effects ------------------ + + def (eff: Effect) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = ??? + + def (effs: Effects) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = + effs.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 01b4b27fb962..ab53b43e989e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -82,4 +82,9 @@ object Potentials { } def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) + + def (pot: Potential) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = ??? + + def (pots: Potentials) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = + pots.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) } \ No newline at end of file From 4eb8eaa748da98735ce8785fe36512203839591d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 17:01:33 +0100 Subject: [PATCH 026/112] WIP - Fix expand --- .../tools/dotc/transform/init/Checking.scala | 258 +++++------------- .../dotc/transform/init/Potentials.scala | 4 +- .../tools/dotc/transform/init/Util.scala | 7 +- 3 files changed, 75 insertions(+), 194 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index ee3fb9b248ba..12493e658292 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -90,12 +90,14 @@ object Checking { case tree => val (_, effs) = Summarization.analyze(tree) - effs.foreach { check(_)(state) } + checkEffects(effs) } } - def checkEffects(effs: Effects): Unit = - effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty).foreach { check(_) } + def checkEffects(effs: Effects): Unit = { + val rebased = effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty) + rebased.foreach { check(_) } + } // check parent calls : follows linearization ordering // see spec 5.1 about "Template Evaluation". @@ -167,23 +169,19 @@ object Checking { (stats :+ expr).foreach { stat => val (_, effs) = Summarization.analyze(stat)(theCtx.withOwner(ctor)) - effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty).foreach { check(_) } - effs.foreach { check(_)(state) } + val rebased = effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty) + rebased.foreach { check(_) } } } - - /** Resolve possible overriding of the term symbol `sym` relative to `thisClass` */ - private def resolveVirtual(thisClass: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = - if (sym.isEffectivelyFinal) sym - else sym.matchingMember(thisClass.typeRef) - private def check(eff: Effect)(implicit state: State): Unit = if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show, init) { eff match { case Leak(pot) => pot match { - case ThisRef(tp) => + case ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + theCtx.warning( "Leaking of this. Calling trace:\n" + state.trace, eff.source.sourcePos @@ -217,11 +215,9 @@ object Checking { case FieldAccess(pot, field) => pot match { case ThisRef(cls) => - if ( - cls eq state.thisClass && - !state.fieldsInited.contains(field) && - !field.is(Flags.Lazy) - ) { + assert(tp == state.thisClass, "unexpected potential " + pot.show) + + if (!state.fieldsInited.contains(field) && !field.is(Flags.Lazy)) { traceIndented("initialized: " + state.fieldsInited, init) // should issue error, use warning so that it will continue compile subprojects @@ -232,9 +228,17 @@ object Checking { ) } - case Warm(cls) => + if (field.is(Flags.Lazy)) { + // TODO: get effects, rebase and check + } + + case Warm(cls, outer) => // all fields of warm values are initialized + if (field.is(Flags.Lazy)) { + // TODO: get effects, rebase and check + } + case Cold(cls, _) => theCtx.warning( "Access field " + eff.source.show + " on a known value under initialization" + @@ -254,38 +258,38 @@ object Checking { case MethodCall(pot, sym, virtual) => pot match { case ThisRef(cls) => - if (cls eq state.thisClass) { - // overriding resolution - val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym - if (targetSym.exists) { // tests/init/override17.scala - val effs = theEnv.effectsOf(targetSym) - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } - } - else { - traceIndented("!!!sym does not exist: " + pot.show) - } - } + assert(cls == state.thisClass, "unexpected potential " + pot.show) - case Warm(cls) => - // overriding resolution - val targetSym = if (virtual) resolveVirtual(cls, sym) else sym - val effs = theEnv.effectsOf(targetSym) - val state2 = state.withVisited(eff) - effs.foreach { eff => - val effs = substitute(eff, Map.empty, Some(cls -> pot)) - effs.foreach { eff2 => check(eff2)(state2) } + if (sym.isInternal) { // tests/init/override17.scala + val cls = sym.owner + val effs = theEnv.summaryOf(cls).effectsOf(sym) + val rebased = effs.asSeenFrom(pot, cls, Potentials.empty) + val state2 = state.withVisited(eff) + rebased.foreach { check(_)(state2) } } + else + theCtx.warning( + "Calling the external method " + sym.show + + " may cause initialization errors" + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) - case Dependent(cls, bindings) => - // overriding resolution - val targetSym = if (virtual) resolveVirtual(cls, sym) else sym - val effs = theEnv.effectsOf(targetSym) - val state2 = state.withVisited(eff) - effs.foreach { eff => - val effs = substitute(eff, bindings, Some(cls -> pot)) - effs.foreach { eff2 => check(eff2)(state2) } + case warm @ Warm(cls, outer) => + if (sym.isInternal) { + val cls = sym.owner + val effs = theEnv.summaryOf(cls).effectsOf(sym) + val rebased = effs.asSeenFrom(pot, cls, warm.outerFor(cls)) + val state2 = state.withVisited(eff) + rebased.foreach { check(_)(state2) } } + else + theCtx.warning( + "Calling the external method " + sym.show + + " on uninitialized objects may cause initialization errors" + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) case Cold(cls, _) => theCtx.warning( @@ -310,96 +314,22 @@ object Checking { check(MethodCall(pot, sym, virtual)(eff.source))(state2) } } - - case DependentCall(pot, sym, bindings) => - assert(sym.isConstructor, "only dependent call of contructor supported") - val state2 = state.withVisited(eff) - pot match { - // `Cold(cls).init(args)`: encoding of new expressions - case Cold(cls, _) => - val effs = theEnv.effectsOf(sym) - if (sym.isPrimaryConstructor) { - val bindings2 = Summarization.dependentParamBindings(sym, bindings) - val potDep = Dependent(cls, bindings2)(eff.source) - effs.foreach { eff => - val effs = substitute(eff, bindings, Some(cls -> potDep)) - effs.foreach { eff2 => check(eff2)(state2) } - } - } - else { - val effs = theEnv.effectsOf(sym) - val potDeps = expand(DependentReturn(pot, sym, bindings)(eff.source)) - assert(potDeps.size == 1, "expect size = 1, found " + potDeps.size) - effs.foreach { eff => - val effs = substitute(eff, bindings, Some(cls -> potDeps.head)) - effs.foreach { eff2 => check(eff2)(state2) } - } - } - - case ThisRef(cls) => - assert( - state.currentClass.derivesFrom(cls), - state.currentClass.show + " not derived from " + cls.show - ) // in current class hierachy - if (sym.exists) { // tests/init/override17.scala - val effs = theEnv.effectsOf(sym) - val state2 = state.withVisited(eff) - effs.foreach { eff => - val effs = substitute(eff, bindings, None) - effs.foreach { eff2 => check(eff2)(state2) } - } - } - else { - traceIndented("!!!sym does not exist: " + pot.show) - } - - case Warm(cls) => - val effs = theEnv.effectsOf(sym) - val state2 = state.withVisited(eff) - effs.foreach { eff => - val effs = substitute(eff, bindings, Some(cls -> pot)) - effs.foreach { eff2 => check(eff2)(state2) } - } - - case Dependent(cls, bindings1) => - val effs = theEnv.effectsOf(sym) - val state2 = state.withVisited(eff) - effs.foreach { eff => - val effs = substitute(eff, bindings, Some(cls -> pot)) - effs.foreach { eff2 => check(eff2)(state2) } - } - - case Fun(pots, effs) => - throw new Exception("Why I'm reached? " + pot.show) - - case pot => - val state2 = state.withVisited(eff) - val pots = expand(pot) - pots.foreach { pot => - check(DependentCall(pot, sym, bindings)(eff.source))(state2) - } - - } } } private def expand(pot: Potential)(implicit state: State): Potentials = - trace("expand " + pot.show, pots => Potentials.show(pots.asInstanceOf[Potentials])) { pot match { + trace("expand " + pot.show, init, pots => Potentials.show(pots.asInstanceOf[Potentials])) { pot match { case MethodReturn(pot1, sym, virtual) => pot1 match { case ThisRef(cls) => - assert( - state.currentClass.derivesFrom(cls), - "current class: " + state.currentClass.show + ", tp.symbol = " + cls - ) - val targetSym = if (virtual) resolveVirtual(state.currentClass, sym) else sym - if (targetSym.exists) { - theEnv.potentialsOf(targetSym) - } - else { - traceIndented("!!!sym does not exist: " + pot.show) - Set.empty + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + if (sym.isInternal) { // tests/init/override17.scala + val cls = sym.owner + val pots = theEnv.summaryOf(cls).potentialsOf(sym) + pots.asSeenFrom(pot1, cls, Potentials.empty) } + else Potentials.empty // warning already issued in call effect case Fun(pots, effs) => val name = sym.name.toString @@ -411,15 +341,13 @@ object Checking { } else Potentials.empty - case Warm(cls) => - val targetSym = if (virtual) resolveVirtual(cls, sym) else sym - val pots = theEnv.potentialsOf(targetSym) - pots.flatMap { pot2 => substitute(pot2, Map.empty, Some(cls -> pot1)) } - - case Dependent(cls, bindings) => - val targetSym = if (virtual) resolveVirtual(cls, sym) else sym - val pots = theEnv.potentialsOf(targetSym) - pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + case warm : Warm => + if (sym.isInternal) { + val cls = sym.owner + val pots = theEnv.summaryOf(cls).potentialsOf(sym) + pots.asSeenFrom(pot1, cls, warm.outerFor(cls)) + } + else Potentials.empty // warning already issued in call effect case Cold(cls, _) => Potentials.empty // error already reported, ignore @@ -467,61 +395,7 @@ object Checking { pots } - case DependentReturn(pot1, sym, bindings) => - pot1 match { - case ThisRef(cls) => - // access to top-level objects - val isPackage = cls.is(Flags.Package) - if (!isPackage) assert( - state.currentClass.derivesFrom(cls), - "current class: " + state.currentClass.show + ", symbol = " + cls - ) - if (isPackage) Set.empty - else { - val pots = theEnv.potentialsOf(sym) - pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } - } - - case Fun(pots, effs) => - throw new Exception("Unexpected code reached") - - case Warm(cls) => - val pots = theEnv.potentialsOf(sym) - pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } - - case Dependent(cls, bindings1) => - val pots = theEnv.potentialsOf(sym) - pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } - - case Cold(cls, _) => - if (sym.isPrimaryConstructor) { - val bindings2 = Summarization.dependentParamBindings(sym, bindings) - Potentials.empty + Dependent(cls, bindings2)(pot.source) - } - else if (sym.isConstructor) { - val pots = theEnv.potentialsOf(sym) - assert(pots.size == 1, "pots = " + Potentials.show(pots)) - substitute(pots.head, bindings, None) - } - else { - Potentials.empty // error already reported, ignore - } - - case _ => - val (pots, effs) = expand(pot1).select(sym, pot.source, bindings = bindings) - effs.foreach(check(_)) - pots - } - - case Var(sym) => - assert(sym.owner.isPrimaryConstructor, "sym = " + sym.show) - assert( - state.currentClass.derivesFrom(sym.owner.owner), - "current = " + state.currentClass.show + ", owner = " + sym.owner.owner.show - ) - Potentials.empty - - case _: ThisRef | _: Fun | _: Warm | _: Cold | _: Dependent => + case _: ThisRef | _: Fun | _: Warm | _: Cold => Set(pot) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index ab53b43e989e..79eb1788f1de 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -40,6 +40,8 @@ object Potentials { case class Warm(cls: ClassSymbol, outer: Potentials)(val source: Tree) extends Potential { def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" + + def outerFor(cls: ClassSymbol)(implicit ctx: Context): Potentials = ??? } case class FieldReturn(potential: Potential, field: Symbol)(val source: Tree) extends Potential { @@ -68,7 +70,7 @@ object Potentials { // ------------------ operations on potentials ------------------ - def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true, bindings: Map[Symbol, Potentials] = Map.empty)(implicit ctx: Context): Summary = + def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true)(implicit ctx: Context): Summary = ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => if (pot.size > 1) (pots, effs + Leak(pot)(source)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 61e30102c4ec..0c7f2f687d5c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -2,7 +2,9 @@ package dotty.tools.dotc package transform package init -import core.Contexts._ +import core._ +import Contexts.Context +import Symbols._ import config.Printers.Printer @@ -15,4 +17,7 @@ object Util { op traceIndented(s"<== ${msg}", printer) } + + def (symbol: Symbol) isInternal(implicit ctx: Context): Boolean = + !symbol.defTree.isEmpty } \ No newline at end of file From 9244e27d08f84ffade101a7296df1f5113d5cd54 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 17:23:19 +0100 Subject: [PATCH 027/112] WIP - Fix more type checking --- .../tools/dotc/transform/init/Checking.scala | 64 +++++++++---------- .../tools/dotc/transform/init/Effects.scala | 4 +- .../dotc/transform/init/Potentials.scala | 6 +- .../tools/dotc/transform/init/Summary.scala | 2 +- 4 files changed, 35 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 12493e658292..aef29cceed8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -12,6 +12,7 @@ import Symbols._ import Constants.Constant import Types._ import util.NoSourcePosition +import reporting.trace import config.Printers.init import Effects._, Potentials._, Summary._, Util._ @@ -113,14 +114,13 @@ object Checking { val cls = ctor.owner val classDef = cls.defTree if (!classDef.isEmpty) { - val (pots, effs) = tp.typeConstructor match { - case tref: TypeRef => Summarization.analyze(tref.prefix, source) - } - // TODO: no need to check, can only be empty - checkEffects(effs) + // val (pots, effs) = tp.typeConstructor match { + // case tref: TypeRef => Summarization.analyze(tref.prefix, source) + // } + // checkEffects(effs) - if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef], pots) + if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef]) else checkSecondaryConstructor(ctor) } else if (!cls.isOneOf(Flags.EffectivelyOpenFlags)) @@ -161,7 +161,7 @@ object Checking { val cls = ctor.owner traceOp("check ctor: " + ctor.show, init) { - if (ctorCallSym.isPrimaryConstructor) + if (ctor.isPrimaryConstructor) checkClassBody(cls.defTree.asInstanceOf[TypeDef]) else checkSecondaryConstructor(ctor) @@ -215,7 +215,7 @@ object Checking { case FieldAccess(pot, field) => pot match { case ThisRef(cls) => - assert(tp == state.thisClass, "unexpected potential " + pot.show) + assert(cls == state.thisClass, "unexpected potential " + pot.show) if (!state.fieldsInited.contains(field) && !field.is(Flags.Lazy)) { traceIndented("initialized: " + state.fieldsInited, init) @@ -227,8 +227,7 @@ object Checking { eff.source.sourcePos ) } - - if (field.is(Flags.Lazy)) { + else if (field.is(Flags.Lazy)) { // TODO: get effects, rebase and check } @@ -261,7 +260,7 @@ object Checking { assert(cls == state.thisClass, "unexpected potential " + pot.show) if (sym.isInternal) { // tests/init/override17.scala - val cls = sym.owner + val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) val rebased = effs.asSeenFrom(pot, cls, Potentials.empty) val state2 = state.withVisited(eff) @@ -277,7 +276,7 @@ object Checking { case warm @ Warm(cls, outer) => if (sym.isInternal) { - val cls = sym.owner + val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) val rebased = effs.asSeenFrom(pot, cls, warm.outerFor(cls)) val state2 = state.withVisited(eff) @@ -325,7 +324,7 @@ object Checking { assert(cls == state.thisClass, "unexpected potential " + pot.show) if (sym.isInternal) { // tests/init/override17.scala - val cls = sym.owner + val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) pots.asSeenFrom(pot1, cls, Potentials.empty) } @@ -343,13 +342,13 @@ object Checking { case warm : Warm => if (sym.isInternal) { - val cls = sym.owner + val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) pots.asSeenFrom(pot1, cls, warm.outerFor(cls)) } else Potentials.empty // warning already issued in call effect - case Cold(cls, _) => + case _: Cold => Potentials.empty // error already reported, ignore case _ => @@ -361,32 +360,27 @@ object Checking { case FieldReturn(pot1, sym) => pot1 match { case ThisRef(cls) => - // access to top-level objects - val isPackage = cls.is(Flags.Package) - if (!isPackage) assert( - state.currentClass.derivesFrom(cls), - "current class: " + state.currentClass.show + ", symbol = " + cls - ) - if (isPackage) Set.empty - else theEnv.potentialsOf(sym).map(devar(_, _ => None)).collect { - case Some(pot) => pot + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + if (sym.isInternal) { // tests/init/override17.scala + val cls = sym.owner.asClass + val pots = theEnv.summaryOf(cls).potentialsOf(sym) + pots.asSeenFrom(pot1, cls, Potentials.empty) } + else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) - case Fun(pots, effs) => + case _: Fun => throw new Exception("Unexpected code reached") - case Warm(cls) => - def toCold(sym: Symbol): Potential = Cold(sym.info.classSymbol.asClass, definite = false)(pot.source) - val pots = theEnv.potentialsOf(sym).map(devar(_, sym => Some(toCold(sym)))).collect { - case Some(pot) => pot + case warm: Warm => + if (sym.isInternal) { + val cls = sym.owner.asClass + val pots = theEnv.summaryOf(cls).potentialsOf(sym) + pots.asSeenFrom(pot1, cls, warm.outerFor(cls)) } - pots.flatMap { pot2 => substitute(pot2, Map.empty, Some(cls -> pot1)) } - - case Dependent(cls, bindings) => - val pots = theEnv.potentialsOf(sym) - pots.flatMap { pot2 => substitute(pot2, bindings, Some(cls -> pot1)) } + else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) - case Cold(cls, _) => + case _: Cold => Potentials.empty // error already reported, ignore case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index ff92c386205d..b09121a45500 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -44,8 +44,8 @@ object Effects { // ------------------ operations on effects ------------------ - def (eff: Effect) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = ??? + def (eff: Effect) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = ??? - def (effs: Effects) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = + def (effs: Effects) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = effs.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 79eb1788f1de..feb41bcbdb40 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -57,7 +57,7 @@ object Potentials { } } - case class Cold(cls: ClassSymbol, definite: Boolean)(val source: Tree) extends Potential { + case class Cold(cls: ClassSymbol)(val source: Tree) extends Potential { def size: Int = 1 def show(implicit ctx: Context): String = "Cold[" + cls.show + "]" } @@ -85,8 +85,8 @@ object Potentials { def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) - def (pot: Potential) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = ??? + def (pot: Potential) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = ??? - def (pots: Potentials) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potential = + def (pots: Potentials) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = pots.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index ea61a91b18ec..d198fdb1cd02 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -23,7 +23,7 @@ object Summary { case class ClassSummary(currentClass: ClassSymbol, parentOuter: Map[ClassSymbol, Potentials]) { private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty - def cacheFor(member: Symbol, summary: Summary): Unit = { + def cacheFor(member: Symbol, summary: Summary)(implicit ctx: Context): Unit = { assert(member.owner == currentClass, "owner = " + member.owner.show + ", current = " + currentClass.show) summaryCache(member) = summary } From 06ee1114c56c7acc5c61a90ac0ae8a1c3d239311 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 17:38:08 +0100 Subject: [PATCH 028/112] Fix compilation: overloading does not work well with extension methods --- .../tools/dotc/transform/init/Checking.scala | 18 +++++++++--------- .../tools/dotc/transform/init/Effects.scala | 6 +++--- .../tools/dotc/transform/init/Potentials.scala | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index aef29cceed8d..468bec12fd4d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -96,7 +96,7 @@ object Checking { } def checkEffects(effs: Effects): Unit = { - val rebased = effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty) + val rebased = Effects.asSeenFrom(effs, ThisRef(state.thisClass)(null), cls, Potentials.empty) rebased.foreach { check(_) } } @@ -158,7 +158,7 @@ object Checking { def checkSecondaryConstructor(ctor: Symbol)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) { val Block(ctorCall :: stats, expr) = ctor.defTree - val cls = ctor.owner + val cls = ctor.owner.asClass traceOp("check ctor: " + ctor.show, init) { if (ctor.isPrimaryConstructor) @@ -169,7 +169,7 @@ object Checking { (stats :+ expr).foreach { stat => val (_, effs) = Summarization.analyze(stat)(theCtx.withOwner(ctor)) - val rebased = effs.asSeenFrom(ThisRef(state.thisClass)(null), cls, Potentials.empty) + val rebased = Effects.asSeenFrom(effs, ThisRef(state.thisClass)(null), cls, Potentials.empty) rebased.foreach { check(_) } } } @@ -262,7 +262,7 @@ object Checking { if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) - val rebased = effs.asSeenFrom(pot, cls, Potentials.empty) + val rebased = Effects.asSeenFrom(effs, pot, cls, Potentials.empty) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -278,7 +278,7 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) - val rebased = effs.asSeenFrom(pot, cls, warm.outerFor(cls)) + val rebased = Effects.asSeenFrom(effs, pot, cls, warm.outerFor(cls)) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -326,7 +326,7 @@ object Checking { if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - pots.asSeenFrom(pot1, cls, Potentials.empty) + Potentials.asSeenFrom(pots, pot1, cls, Potentials.empty) } else Potentials.empty // warning already issued in call effect @@ -344,7 +344,7 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - pots.asSeenFrom(pot1, cls, warm.outerFor(cls)) + Potentials.asSeenFrom(pots, pot1, cls, warm.outerFor(cls)) } else Potentials.empty // warning already issued in call effect @@ -365,7 +365,7 @@ object Checking { if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - pots.asSeenFrom(pot1, cls, Potentials.empty) + Potentials.asSeenFrom(pots, pot1, cls, Potentials.empty) } else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) @@ -376,7 +376,7 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - pots.asSeenFrom(pot1, cls, warm.outerFor(cls)) + Potentials.asSeenFrom(pots, pot1, cls, warm.outerFor(cls)) } else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index b09121a45500..039b55f711d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -44,8 +44,8 @@ object Effects { // ------------------ operations on effects ------------------ - def (eff: Effect) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = ??? + def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = ??? - def (effs: Effects) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = - effs.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) + def asSeenFrom(effs: Effects, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = + effs.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index feb41bcbdb40..4479c7462b0f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -85,8 +85,8 @@ object Potentials { def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) - def (pot: Potential) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = ??? + def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = ??? - def (pots: Potentials) asSeenFrom(thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = - pots.flatMap(_.asSeenFrom(thisValue, currentClass, outer)) + def asSeenFrom(pots: Potentials, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = + pots.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) } \ No newline at end of file From e923bf4f1e6deda532530d95b3f3391e673a80da Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 13 Dec 2019 18:17:19 +0100 Subject: [PATCH 029/112] Fix compilation warnings --- .../dotty/tools/dotc/transform/init/Checking.scala | 13 ++++++++----- .../tools/dotc/transform/init/Potentials.scala | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 468bec12fd4d..9cf1ef928a07 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -187,7 +187,7 @@ object Checking { eff.source.sourcePos ) - case Cold(cls, _) => + case _: Cold => theCtx.warning( "Leaking of cold value " + eff.source.show + ". Calling trace:\n" + state.trace, @@ -238,7 +238,7 @@ object Checking { // TODO: get effects, rebase and check } - case Cold(cls, _) => + case _: Cold => theCtx.warning( "Access field " + eff.source.show + " on a known value under initialization" + ". Calling trace:\n" + state.trace, @@ -290,7 +290,7 @@ object Checking { eff.source.sourcePos ) - case Cold(cls, _) => + case _: Cold => theCtx.warning( "Call method " + eff.source.show + " on a cold value" + ". Calling trace:\n" + state.trace, @@ -367,7 +367,7 @@ object Checking { val pots = theEnv.summaryOf(cls).potentialsOf(sym) Potentials.asSeenFrom(pots, pot1, cls, Potentials.empty) } - else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) + else Potentials.empty + Cold()(pot.source) case _: Fun => throw new Exception("Unexpected code reached") @@ -378,7 +378,7 @@ object Checking { val pots = theEnv.summaryOf(cls).potentialsOf(sym) Potentials.asSeenFrom(pots, pot1, cls, warm.outerFor(cls)) } - else Potentials.empty + Cold(sym.info.classSymbol.asClass)(pot.source) + else Potentials.empty + Cold()(pot.source) case _: Cold => Potentials.empty // error already reported, ignore @@ -391,6 +391,9 @@ object Checking { case _: ThisRef | _: Fun | _: Warm | _: Cold => Set(pot) + + case _: SuperRef => + throw new Exception("Unexpected SuperRef") } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 4479c7462b0f..0e4105ad8873 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -57,9 +57,9 @@ object Potentials { } } - case class Cold(cls: ClassSymbol)(val source: Tree) extends Potential { + case class Cold()(val source: Tree) extends Potential { def size: Int = 1 - def show(implicit ctx: Context): String = "Cold[" + cls.show + "]" + def show(implicit ctx: Context): String = "Cold" } case class Fun(potentials: Potentials, effects: Effects)(val source: Tree) extends Potential { From 617a6d997346e4936b25a72167e0048f595f8d37 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 02:02:25 +0100 Subject: [PATCH 030/112] Handle outer --- .../tools/dotc/transform/init/Checking.scala | 29 +++++++++++++++++-- .../dotc/transform/init/Potentials.scala | 29 ++++++++++++++++++- .../tools/dotc/transform/init/Summary.scala | 14 ++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 9cf1ef928a07..f791dd1fa832 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -278,7 +278,8 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) - val rebased = Effects.asSeenFrom(effs, pot, cls, warm.outerFor(cls)) + val outer = Potentials.empty + Outer(warm, cls)(warm.source) + val rebased = Effects.asSeenFrom(effs, pot, cls, outer) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -344,7 +345,8 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, pot1, cls, warm.outerFor(cls)) + val outer = Potentials.empty + Outer(warm, cls)(warm.source) + Potentials.asSeenFrom(pots, pot1, cls, outer) } else Potentials.empty // warning already issued in call effect @@ -376,7 +378,8 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, pot1, cls, warm.outerFor(cls)) + val outer = Potentials.empty + (Outer(warm, cls)(warm.source)) + Potentials.asSeenFrom(pots, pot1, cls, outer) } else Potentials.empty + Cold()(pot.source) @@ -389,6 +392,26 @@ object Checking { pots } + case Outer(pot1, cls) => + pot1 match { + case ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + Potentials.empty + + case _: Fun => + throw new Exception("Unexpected code reached") + + case warm: Warm => + warm.outerFor(cls) + + case _: Cold => + throw new Exception("Unexpected code reached") + + case _ => + expand(pot1).map { Outer(_, cls)(pot.source) } + } + case _: ThisRef | _: Fun | _: Warm | _: Cold => Set(pot) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 0e4105ad8873..997962a44f49 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -2,6 +2,8 @@ package dotty.tools.dotc package transform package init +import scala.collection.mutable + import ast.tpd._ import core._ import Types._, Symbols._, Contexts._ @@ -38,10 +40,35 @@ object Potentials { * @param outer The potentials for the immdiate outer `this` */ case class Warm(cls: ClassSymbol, outer: Potentials)(val source: Tree) extends Potential { + assert(outer.size <= 1, "outer size > 1, outer = " + outer) + def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" - def outerFor(cls: ClassSymbol)(implicit ctx: Context): Potentials = ??? + private val outerCache: mutable.Map[ClassSymbol, Potentials] = mutable.Map.empty + def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = + if (outerCache.contains(cls)) outerCache(cls) + else if (cls `eq` this.cls) outer + else { + val bottomClsSummary = env.summaryOf(this.cls) + val objPart = ObjectPart(this, this.cls, outer, bottomClsSummary.parentOuter) + val pots = objPart.outerFor(cls) + outerCache(cls) = pots + pots + } + } + + /** The Outer potential for `classSymbol` of the object `pot` + * + * It's only used internally for expansion of potentials. + * + * Note: Usage of `Type.baseType(cls)` may simplify the code. + * Current implementation avoids using complex type machinary, + * and may be potentially faster. + */ + case class Outer(pot: Potential, classSymbol: ClassSymbol)(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = "Outer[" + pot.show + ", " + classSymbol.show + "]" } case class FieldReturn(potential: Potential, field: Symbol)(val source: Tree) extends Potential { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index d198fdb1cd02..c9d50f9f3315 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -56,7 +56,7 @@ object Summary { * It makes prefix and outer substitution easier in effect checking. */ case class ObjectPart( - thisValue: Warm | ThisRef, // the potential for `this`, it can be Warm or ThisRef + thisValue: Warm, // the potential for `this`, it can be Warm or ThisRef currentClass: ClassSymbol, // current class currentOuter: Potentials, // the immediate outer for current class, empty for local and top-level classes parentOuter: Map[ClassSymbol, Potentials] // outers for direct parents @@ -66,6 +66,18 @@ object Summary { /** Summary of a member field or method, with `this` and outers substituted */ def summaryOf(member: Symbol)(implicit ctx: Context): Summary = ??? + def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = + if (cls `eq` currentClass) currentOuter + else parentOuter.find((k, v) => k.derivesFrom(cls)) match { + case Some((parentCls, pots)) => + val bottomClsSummary = env.summaryOf(parentCls) + val outerOuter: Potentials = currentOuter.map{ pot => Outer(pot, currentClass)(pot.source) } + val rebased: Potentials = currentOuter.flatMap { pot => Potentials.asSeenFrom(pots, pot, currentClass, outerOuter) } + val objPart = ObjectPart(thisValue, parentCls, rebased, bottomClsSummary.parentOuter) + objPart.outerFor(cls) + case None => ??? // impossible + } + def show(implicit ctx: Context): String = "ObjectPart(this = " + thisValue.show + "," + currentClass.name.show + ", outer = " + Potentials.show(currentOuter) + "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" From 4f76c6abfccd0723d79f8392b784897653642aa1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 03:34:13 +0100 Subject: [PATCH 031/112] WIP - asSeenFrom --- .../tools/dotc/transform/init/Checking.scala | 18 ++--- .../dotc/transform/init/Potentials.scala | 68 +++++++++++++++---- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index f791dd1fa832..bd993cd9bbd3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -256,13 +256,13 @@ object Checking { case MethodCall(pot, sym, virtual) => pot match { - case ThisRef(cls) => + case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) - val rebased = Effects.asSeenFrom(effs, pot, cls, Potentials.empty) + val rebased = Effects.asSeenFrom(effs, thisRef, cls, Potentials.empty) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -279,7 +279,7 @@ object Checking { val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) val outer = Potentials.empty + Outer(warm, cls)(warm.source) - val rebased = Effects.asSeenFrom(effs, pot, cls, outer) + val rebased = Effects.asSeenFrom(effs, warm, cls, outer) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -321,13 +321,13 @@ object Checking { trace("expand " + pot.show, init, pots => Potentials.show(pots.asInstanceOf[Potentials])) { pot match { case MethodReturn(pot1, sym, virtual) => pot1 match { - case ThisRef(cls) => + case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, pot1, cls, Potentials.empty) + Potentials.asSeenFrom(pots, thisRef, cls, Potentials.empty) } else Potentials.empty // warning already issued in call effect @@ -346,7 +346,7 @@ object Checking { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) val outer = Potentials.empty + Outer(warm, cls)(warm.source) - Potentials.asSeenFrom(pots, pot1, cls, outer) + Potentials.asSeenFrom(pots, warm, cls, outer) } else Potentials.empty // warning already issued in call effect @@ -361,13 +361,13 @@ object Checking { case FieldReturn(pot1, sym) => pot1 match { - case ThisRef(cls) => + case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) if (sym.isInternal) { // tests/init/override17.scala val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, pot1, cls, Potentials.empty) + Potentials.asSeenFrom(pots, thisRef, cls, Potentials.empty) } else Potentials.empty + Cold()(pot.source) @@ -379,7 +379,7 @@ object Checking { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) val outer = Potentials.empty + (Outer(warm, cls)(warm.source)) - Potentials.asSeenFrom(pots, pot1, cls, outer) + Potentials.asSeenFrom(pots, warm, cls, outer) } else Potentials.empty + Cold()(pot.source) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 997962a44f49..96d68de5a414 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -23,23 +23,27 @@ object Potentials { def source: Tree } - case class ThisRef(cls: ClassSymbol)(val source: Tree) extends Potential { + sealed trait RootPotential extends Potential { + def classSymbol: ClassSymbol + } + + case class ThisRef(classSymbol: ClassSymbol)(val source: Tree) extends RootPotential { val size: Int = 1 - def show(implicit ctx: Context): String = cls.name.show + ".this" + def show(implicit ctx: Context): String = classSymbol.name.show + ".this" } - case class SuperRef(cls: ClassSymbol, supercls: ClassSymbol)(val source: Tree) extends Potential { + case class SuperRef(root: RootPotential, supercls: ClassSymbol)(val source: Tree) extends Potential { val size: Int = 1 - def show(implicit ctx: Context): String = cls.name.show + ".super[" + supercls.name.show + "]" + def show(implicit ctx: Context): String = root.show + ".super[" + supercls.name.show + "]" } /** A warm potential represents an object of which all fields are initialized, but it may contain * reference to objects under initialization. * - * @param cls The concrete class of the object - * @param outer The potentials for the immdiate outer `this` + * @param classSymbol The concrete class of the object + * @param outer The potentials for the immdiate outer `this` */ - case class Warm(cls: ClassSymbol, outer: Potentials)(val source: Tree) extends Potential { + case class Warm(classSymbol: ClassSymbol, outer: Potentials)(val source: Tree) extends RootPotential { assert(outer.size <= 1, "outer size > 1, outer = " + outer) def size: Int = 1 @@ -48,10 +52,10 @@ object Potentials { private val outerCache: mutable.Map[ClassSymbol, Potentials] = mutable.Map.empty def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = if (outerCache.contains(cls)) outerCache(cls) - else if (cls `eq` this.cls) outer + else if (cls `eq` classSymbol) outer else { - val bottomClsSummary = env.summaryOf(this.cls) - val objPart = ObjectPart(this, this.cls, outer, bottomClsSummary.parentOuter) + val bottomClsSummary = env.summaryOf(classSymbol) + val objPart = ObjectPart(this, classSymbol, outer, bottomClsSummary.parentOuter) val pots = objPart.outerFor(cls) outerCache(cls) = pots pots @@ -112,8 +116,48 @@ object Potentials { def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) - def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = ??? + def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = + pot match { + case MethodReturn(pot1, sym, virtual) => + val pots = asSeenFrom(pot1, thisValue, currentClass, outer) + pots.map { MethodReturn(_, sym, virtual)(pot.source) } + + case FieldReturn(pot1, sym) => + val pots = asSeenFrom(pot1, thisValue, currentClass, outer) + pots.map { FieldReturn(_, sym)(pot.source) } + + case Outer(pot1, cls) => + val pots = asSeenFrom(pot1, thisValue, currentClass, outer) + pots map { Outer(_, cls)(pot.source) } + + case ThisRef(cls) => + if (cls `eq` currentClass) Potentials.empty + thisValue + else if (cls `eq` currentClass.owner) outer + else ??? + + case Fun(pots, effs) => + val pots1 = asSeenFrom(pots, thisValue, currentClass, outer) + val effs1 = Effects.asSeenFrom(effs, thisValue, currentClass, outer) + Fun(pots1, effs1)(pot.source) + + case Warm(cls, outer2) => + // widening to terminate + val thisValue2 = thisValue match { + case Warm(cls, outer) => Warm(cls, Cold()(outer.source))(thisValue.source) + case _ => thisValue + } + + val outer3 = asSeenFrom(outer2, thisValue2, currentClass, outer) + Warm(cls, outer3)(pot.source) + + case _: Cold => + Potentials.empty + pot + + case SuperRef(root, supercls) => + val pots = asSeenFrom(root, thisValue, currentClass, outer) + pots.map { SuperRef(_, supercls)(pot.source) } + } - def asSeenFrom(pots: Potentials, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = + def asSeenFrom(pots: Potentials, thisValue: RootPotential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = pots.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) } \ No newline at end of file From 554620ca2fadef9f24c9033665048bb9e40e4b2e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 12:12:23 +0100 Subject: [PATCH 032/112] Fix Potentials.asSeenFrom --- .../tools/dotc/transform/init/Checking.scala | 16 +++---- .../tools/dotc/transform/init/Effects.scala | 2 + .../dotc/transform/init/Potentials.scala | 43 +++++++++++-------- .../dotc/transform/init/Summarization.scala | 17 ++++++-- .../tools/dotc/transform/init/Summary.scala | 3 +- 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index bd993cd9bbd3..2d15294f55dc 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -278,8 +278,8 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val effs = theEnv.summaryOf(cls).effectsOf(sym) - val outer = Potentials.empty + Outer(warm, cls)(warm.source) - val rebased = Effects.asSeenFrom(effs, warm, cls, outer) + val outer = Outer(warm, cls)(warm.source) + val rebased = Effects.asSeenFrom(effs, warm, cls, outer.toPots) val state2 = state.withVisited(eff) rebased.foreach { check(_)(state2) } } @@ -345,8 +345,8 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - val outer = Potentials.empty + Outer(warm, cls)(warm.source) - Potentials.asSeenFrom(pots, warm, cls, outer) + val outer = Outer(warm, cls)(warm.source) + Potentials.asSeenFrom(pots, warm, cls, outer.toPots) } else Potentials.empty // warning already issued in call effect @@ -369,7 +369,7 @@ object Checking { val pots = theEnv.summaryOf(cls).potentialsOf(sym) Potentials.asSeenFrom(pots, thisRef, cls, Potentials.empty) } - else Potentials.empty + Cold()(pot.source) + else Cold()(pot.source).toPots case _: Fun => throw new Exception("Unexpected code reached") @@ -378,10 +378,10 @@ object Checking { if (sym.isInternal) { val cls = sym.owner.asClass val pots = theEnv.summaryOf(cls).potentialsOf(sym) - val outer = Potentials.empty + (Outer(warm, cls)(warm.source)) - Potentials.asSeenFrom(pots, warm, cls, outer) + val outer = Outer(warm, cls)(warm.source) + Potentials.asSeenFrom(pots, warm, cls, outer.toPots) } - else Potentials.empty + Cold()(pot.source) + else Cold()(pot.source).toPots case _: Cold => Potentials.empty // error already reported, ignore diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 039b55f711d8..5008a758e18b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -44,6 +44,8 @@ object Effects { // ------------------ operations on effects ------------------ + def (eff: Effect) toEffs: Effects = Effects.empty + eff + def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = ??? def asSeenFrom(effs: Effects, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 96d68de5a414..7edecd3451ab 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -32,30 +32,30 @@ object Potentials { def show(implicit ctx: Context): String = classSymbol.name.show + ".this" } - case class SuperRef(root: RootPotential, supercls: ClassSymbol)(val source: Tree) extends Potential { + case class SuperRef(pot: Potential, supercls: ClassSymbol)(val source: Tree) extends Potential { val size: Int = 1 - def show(implicit ctx: Context): String = root.show + ".super[" + supercls.name.show + "]" + def show(implicit ctx: Context): String = pot.show + ".super[" + supercls.name.show + "]" } /** A warm potential represents an object of which all fields are initialized, but it may contain * reference to objects under initialization. * * @param classSymbol The concrete class of the object - * @param outer The potentials for the immdiate outer `this` + * @param outer The potential for `this` of the enclosing class */ - case class Warm(classSymbol: ClassSymbol, outer: Potentials)(val source: Tree) extends RootPotential { + case class Warm(classSymbol: ClassSymbol, outer: Potential)(val source: Tree) extends RootPotential { assert(outer.size <= 1, "outer size > 1, outer = " + outer) def size: Int = 1 - def show(implicit ctx: Context): String = "Warm[" + cls.show + "]" + def show(implicit ctx: Context): String = "Warm[" + classSymbol.show + "]" private val outerCache: mutable.Map[ClassSymbol, Potentials] = mutable.Map.empty def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = if (outerCache.contains(cls)) outerCache(cls) - else if (cls `eq` classSymbol) outer + else if (cls `eq` classSymbol) outer.toPots else { val bottomClsSummary = env.summaryOf(classSymbol) - val objPart = ObjectPart(this, classSymbol, outer, bottomClsSummary.parentOuter) + val objPart = ObjectPart(this, classSymbol, outer.toPots, bottomClsSummary.parentOuter) val pots = objPart.outerFor(cls) outerCache(cls) = pots pots @@ -101,6 +101,8 @@ object Potentials { // ------------------ operations on potentials ------------------ + def (pot: Potential) toPots: Potentials = Potentials.empty + pot + def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true)(implicit ctx: Context): Summary = ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => if (pot.size > 1) @@ -131,14 +133,21 @@ object Potentials { pots map { Outer(_, cls)(pot.source) } case ThisRef(cls) => - if (cls `eq` currentClass) Potentials.empty + thisValue - else if (cls `eq` currentClass.owner) outer - else ??? + if (cls `eq` currentClass) + thisValue.toPots + else if (cls.is(Flags.Package)) + Potentials.empty + else { + val outerCls = currentClass.enclosingClass.asClass + outer.flatMap { out => + asSeenFrom(pot, out, outerCls, Outer(out, outerCls)(out.source).toPots) + } + } case Fun(pots, effs) => - val pots1 = asSeenFrom(pots, thisValue, currentClass, outer) + val pots1 = Potentials.asSeenFrom(pots, thisValue, currentClass, outer) val effs1 = Effects.asSeenFrom(effs, thisValue, currentClass, outer) - Fun(pots1, effs1)(pot.source) + Fun(pots1, effs1)(pot.source).toPots case Warm(cls, outer2) => // widening to terminate @@ -148,16 +157,16 @@ object Potentials { } val outer3 = asSeenFrom(outer2, thisValue2, currentClass, outer) - Warm(cls, outer3)(pot.source) + outer3.map { Warm(cls, _)(pot.source) } case _: Cold => - Potentials.empty + pot + pot.toPots - case SuperRef(root, supercls) => - val pots = asSeenFrom(root, thisValue, currentClass, outer) + case SuperRef(pot, supercls) => + val pots = asSeenFrom(pot, thisValue, currentClass, outer) pots.map { SuperRef(_, supercls)(pot.source) } } - def asSeenFrom(pots: Potentials, thisValue: RootPotential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = + def asSeenFrom(pots: Potentials, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = pots.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 10e7c0c86f4d..ac29855a5ada 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -43,7 +43,8 @@ object Summarization { case supert: Super => val SuperType(thisTp, superTp) = supert.tpe.asInstanceOf[SuperType] - val pot = SuperRef(thisTp.classSymbol.asClass, superTp.classSymbol.asClass)(supert) + val thisRef = ThisRef(thisTp.classSymbol.asClass)(supert) + val pot = SuperRef(thisRef, superTp.classSymbol.asClass)(supert) Summary.empty + pot case Select(qualifier, name) => @@ -73,11 +74,18 @@ object Summarization { case tref: TypeRef => val cls = tref.classSymbol.asClass // local class may capture, thus we need to track it - if (tref.prefix == NoPrefix) Summary.empty + Warm(cls, Potentials.empty)(expr) + if (tref.prefix == NoPrefix) { + val enclosingCls = cls.enclosingClass.asClass + val thisRef = ThisRef(enclosingCls)(expr) + Summary.empty + Warm(cls, thisRef)(expr) + } else { val (pots, effs) = analyze(tref.prefix, expr) if (pots.isEmpty) Summary.empty.withEffs(effs) - else Summary.empty + Warm(cls, pots)(expr) + else { + assert(pots.size == 1) + Summary.empty + Warm(cls, pots.head)(expr) + } } } @@ -194,7 +202,8 @@ object Summarization { val cls = thisTp.widen.classSymbol.asClass Summary.empty + ThisRef(cls)(source) case SuperType(thisTp, superTp) => - val pot = SuperRef(thisTp.classSymbol.asClass, superTp.classSymbol.asClass)(source) + val thisRef = ThisRef(thisTp.classSymbol.asClass)(source) + val pot = SuperRef(thisRef, superTp.classSymbol.asClass)(source) Summary.empty + pot } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index c9d50f9f3315..dcde4ae61269 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -71,8 +71,7 @@ object Summary { else parentOuter.find((k, v) => k.derivesFrom(cls)) match { case Some((parentCls, pots)) => val bottomClsSummary = env.summaryOf(parentCls) - val outerOuter: Potentials = currentOuter.map{ pot => Outer(pot, currentClass)(pot.source) } - val rebased: Potentials = currentOuter.flatMap { pot => Potentials.asSeenFrom(pots, pot, currentClass, outerOuter) } + val rebased: Potentials = Potentials.asSeenFrom(pots, thisValue, currentClass, currentOuter) val objPart = ObjectPart(thisValue, parentCls, rebased, bottomClsSummary.parentOuter) objPart.outerFor(cls) case None => ??? // impossible From 949a009ab2ec74f2e162c75a1a084a5315185d37 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 22:43:00 +0100 Subject: [PATCH 033/112] Fix Effects.asSeenFrom --- .../tools/dotc/transform/init/Effects.scala | 16 +++++++++++++++- .../tools/dotc/transform/init/Summary.scala | 3 --- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 5008a758e18b..c5c6fa033e62 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -46,7 +46,21 @@ object Effects { def (eff: Effect) toEffs: Effects = Effects.empty + eff - def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = ??? + def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = + eff match { + case Leak(pot) => + Potentials.asSeenFrom(pot, thisValue, currentClass, outer).leak(eff.source) + + case FieldAccess(pot, field) => + Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot => + FieldAccess(pot, field)(eff.source) + } + + case MethodCall(pot, sym, virtual) => + Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot => + MethodCall(pot, sym, virtual)(eff.source) + } + } def asSeenFrom(effs: Effects, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = effs.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index dcde4ae61269..2c531baff741 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -63,9 +63,6 @@ object Summary { ) { private val summaryCache: mutable.Map[Symbol, Summary] = mutable.Map.empty - /** Summary of a member field or method, with `this` and outers substituted */ - def summaryOf(member: Symbol)(implicit ctx: Context): Summary = ??? - def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = if (cls `eq` currentClass) currentOuter else parentOuter.find((k, v) => k.derivesFrom(cls)) match { From 5c59fee8e64353073d3c5be15f40c09297d6c61e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 22:55:44 +0100 Subject: [PATCH 034/112] Lazy access as method calls --- .../dotty/tools/dotc/transform/init/Checking.scala | 11 +++-------- .../dotty/tools/dotc/transform/init/Potentials.scala | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 2d15294f55dc..a4329b8af313 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -213,11 +213,13 @@ object Checking { } case FieldAccess(pot, field) => + assert(!field.is(Flags.Lazy)) + pot match { case ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) - if (!state.fieldsInited.contains(field) && !field.is(Flags.Lazy)) { + if (!state.fieldsInited.contains(field)) { traceIndented("initialized: " + state.fieldsInited, init) // should issue error, use warning so that it will continue compile subprojects @@ -227,17 +229,10 @@ object Checking { eff.source.sourcePos ) } - else if (field.is(Flags.Lazy)) { - // TODO: get effects, rebase and check - } case Warm(cls, outer) => // all fields of warm values are initialized - if (field.is(Flags.Lazy)) { - // TODO: get effects, rebase and check - } - case _: Cold => theCtx.warning( "Access field " + eff.source.show + " on a known value under initialization" + diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index 7edecd3451ab..fbe10b8e9ead 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -107,7 +107,7 @@ object Potentials { ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => if (pot.size > 1) (pots, effs + Leak(pot)(source)) - else if (symbol.is(Flags.Method)) + else if (symbol.isOneOf(Flags.Method | Flags.Lazy)) ( pots + MethodReturn(pot, symbol, virtual)(source), effs + MethodCall(pot, symbol, virtual)(source) From 13ce15ed0d94eccfd359611e436d563d8cc050f2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 23:09:44 +0100 Subject: [PATCH 035/112] Remove virtual from MethodCall and MethodReturn --- .../tools/dotc/transform/init/Checking.scala | 14 ++++---------- .../tools/dotc/transform/init/Effects.scala | 11 ++++------- .../tools/dotc/transform/init/Potentials.scala | 17 +++++++---------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index a4329b8af313..a6ade07e4ed3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -114,12 +114,6 @@ object Checking { val cls = ctor.owner val classDef = cls.defTree if (!classDef.isEmpty) { - // TODO: no need to check, can only be empty - // val (pots, effs) = tp.typeConstructor match { - // case tref: TypeRef => Summarization.analyze(tref.prefix, source) - // } - // checkEffects(effs) - if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef]) else checkSecondaryConstructor(ctor) } @@ -249,7 +243,7 @@ object Checking { pots.foreach { pot => check(FieldAccess(pot, field)(eff.source))(state2) } } - case MethodCall(pot, sym, virtual) => + case MethodCall(pot, sym) => pot match { case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) @@ -306,7 +300,7 @@ object Checking { val state2 = state.withVisited(eff) val pots = expand(pot) pots.foreach { pot => - check(MethodCall(pot, sym, virtual)(eff.source))(state2) + check(MethodCall(pot, sym)(eff.source))(state2) } } } @@ -314,7 +308,7 @@ object Checking { private def expand(pot: Potential)(implicit state: State): Potentials = trace("expand " + pot.show, init, pots => Potentials.show(pots.asInstanceOf[Potentials])) { pot match { - case MethodReturn(pot1, sym, virtual) => + case MethodReturn(pot1, sym) => pot1 match { case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) @@ -349,7 +343,7 @@ object Checking { Potentials.empty // error already reported, ignore case _ => - val (pots, effs) = expand(pot1).select(sym, pot.source, virtual) + val (pots, effs) = expand(pot1).select(sym, pot.source) effs.foreach(check(_)) pots } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index c5c6fa033e62..8d65143a40e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -34,12 +34,9 @@ object Effects { potential.show + "." + field.name.show + "!" } - case class MethodCall(potential: Potential, method: Symbol, virtual: Boolean)(val source: Tree) extends Effect { + case class MethodCall(potential: Potential, method: Symbol)(val source: Tree) extends Effect { def size: Int = potential.size - def show(implicit ctx: Context): String = { - val modifier = if (virtual) "" else "(static)" - potential.show + "." + method.name.show + "!" + modifier - } + def show(implicit ctx: Context): String = potential.show + "." + method.name.show + "!" } // ------------------ operations on effects ------------------ @@ -56,9 +53,9 @@ object Effects { FieldAccess(pot, field)(eff.source) } - case MethodCall(pot, sym, virtual) => + case MethodCall(pot, sym) => Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot => - MethodCall(pot, sym, virtual)(eff.source) + MethodCall(pot, sym)(eff.source) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index fbe10b8e9ead..d1a5f6a48b74 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -80,12 +80,9 @@ object Potentials { def show(implicit ctx: Context): String = potential.show + "." + field.name.show } - case class MethodReturn(potential: Potential, symbol: Symbol, virtual: Boolean)(val source: Tree) extends Potential { + case class MethodReturn(potential: Potential, symbol: Symbol)(val source: Tree) extends Potential { def size: Int = potential.size + 1 - def show(implicit ctx: Context): String = { - val modifier = if (virtual) "" else "(static)" - potential.show + "." + symbol.name.show + modifier - } + def show(implicit ctx: Context): String = potential.show + "." + symbol.name.show } case class Cold()(val source: Tree) extends Potential { @@ -103,14 +100,14 @@ object Potentials { def (pot: Potential) toPots: Potentials = Potentials.empty + pot - def (ps: Potentials) select (symbol: Symbol, source: Tree, virtual: Boolean = true)(implicit ctx: Context): Summary = + def (ps: Potentials) select (symbol: Symbol, source: Tree)(implicit ctx: Context): Summary = ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => if (pot.size > 1) (pots, effs + Leak(pot)(source)) else if (symbol.isOneOf(Flags.Method | Flags.Lazy)) ( - pots + MethodReturn(pot, symbol, virtual)(source), - effs + MethodCall(pot, symbol, virtual)(source) + pots + MethodReturn(pot, symbol)(source), + effs + MethodCall(pot, symbol)(source) ) else (pots + FieldReturn(pot, symbol)(source), effs + FieldAccess(pot, symbol)(source)) @@ -120,9 +117,9 @@ object Potentials { def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = pot match { - case MethodReturn(pot1, sym, virtual) => + case MethodReturn(pot1, sym) => val pots = asSeenFrom(pot1, thisValue, currentClass, outer) - pots.map { MethodReturn(_, sym, virtual)(pot.source) } + pots.map { MethodReturn(_, sym)(pot.source) } case FieldReturn(pot1, sym) => val pots = asSeenFrom(pot1, thisValue, currentClass, outer) From 917ac3080dd4706bf94ce4d3f8584b5fe4d32f6a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 14 Dec 2019 23:12:05 +0100 Subject: [PATCH 036/112] Remove RootPotential --- .../src/dotty/tools/dotc/transform/init/Potentials.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index d1a5f6a48b74..ecdfbfb9a12f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -23,11 +23,7 @@ object Potentials { def source: Tree } - sealed trait RootPotential extends Potential { - def classSymbol: ClassSymbol - } - - case class ThisRef(classSymbol: ClassSymbol)(val source: Tree) extends RootPotential { + case class ThisRef(classSymbol: ClassSymbol)(val source: Tree) extends Potential { val size: Int = 1 def show(implicit ctx: Context): String = classSymbol.name.show + ".this" } @@ -43,7 +39,7 @@ object Potentials { * @param classSymbol The concrete class of the object * @param outer The potential for `this` of the enclosing class */ - case class Warm(classSymbol: ClassSymbol, outer: Potential)(val source: Tree) extends RootPotential { + case class Warm(classSymbol: ClassSymbol, outer: Potential)(val source: Tree) extends Potential { assert(outer.size <= 1, "outer size > 1, outer = " + outer) def size: Int = 1 From 5d34ea167fc80cb7fedf8a31da52425c6d9bdaab Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 00:10:14 +0100 Subject: [PATCH 037/112] Code refactoring --- .../tools/dotc/transform/init/Checking.scala | 127 ++++++++++-------- .../dotc/transform/init/Potentials.scala | 41 ++++++ .../tools/dotc/transform/init/Util.scala | 4 + 3 files changed, 116 insertions(+), 56 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index a6ade07e4ed3..1c97ae97a462 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -168,6 +168,25 @@ object Checking { } } + private def nonInitError(eff: Effect)(implicit state: State) = { + traceIndented("initialized: " + state.fieldsInited, init) + + // should issue error, use warning so that it will continue compile subprojects + theCtx.warning( + "Access non-initialized field " + eff.source.show + + ". Calling trace:\n" + state.trace, + eff.source.sourcePos + ) + } + + private def externalCallError(sym: Symbol, source: Tree)(implicit state: State) = + theCtx.warning( + "Calling the external method " + sym.show + + " may cause initialization errors" + + ". Calling trace:\n" + state.trace, + source.sourcePos + ) + private def check(eff: Effect)(implicit state: State): Unit = if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show, init) { eff match { @@ -213,16 +232,14 @@ object Checking { case ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) - if (!state.fieldsInited.contains(field)) { - traceIndented("initialized: " + state.fieldsInited, init) + val target = resolve(cls, field) + if (!state.fieldsInited.contains(target)) nonInitError(eff) - // should issue error, use warning so that it will continue compile subprojects - theCtx.warning( - "Access non-initialized field " + eff.source.show + - ". Calling trace:\n" + state.trace, - eff.source.sourcePos - ) - } + case SuperRef(ThisRef(cls), supercls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolveSuper(cls, supercls, field) + if (!state.fieldsInited.contains(target)) nonInitError(eff) case Warm(cls, outer) => // all fields of warm values are initialized @@ -248,37 +265,34 @@ object Checking { case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) - if (sym.isInternal) { // tests/init/override17.scala - val cls = sym.owner.asClass - val effs = theEnv.summaryOf(cls).effectsOf(sym) - val rebased = Effects.asSeenFrom(effs, thisRef, cls, Potentials.empty) + val target = resolve(cls, sym) + if (target.isInternal) { // tests/init/override17.scala + val effs = thisRef.effectsOf(target) + val state2 = state.withVisited(eff) + effs.foreach { check(_)(state2) } + } + else externalCallError(target, eff.source) + + case SuperRef(thisRef @ ThisRef(cls), supercls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolveSuper(cls, supercls, sym) + if (target.isInternal) { + val effs = thisRef.effectsOf(target) val state2 = state.withVisited(eff) - rebased.foreach { check(_)(state2) } + effs.foreach { check(_)(state2) } } - else - theCtx.warning( - "Calling the external method " + sym.show + - " may cause initialization errors" + - ". Calling trace:\n" + state.trace, - eff.source.sourcePos - ) + else externalCallError(target, eff.source) case warm @ Warm(cls, outer) => - if (sym.isInternal) { - val cls = sym.owner.asClass - val effs = theEnv.summaryOf(cls).effectsOf(sym) - val outer = Outer(warm, cls)(warm.source) - val rebased = Effects.asSeenFrom(effs, warm, cls, outer.toPots) + val target = resolve(cls, sym) + + if (target.isInternal) { + val effs = warm.effectsOf(target) val state2 = state.withVisited(eff) - rebased.foreach { check(_)(state2) } + effs.foreach { check(_)(state2) } } - else - theCtx.warning( - "Calling the external method " + sym.show + - " on uninitialized objects may cause initialization errors" + - ". Calling trace:\n" + state.trace, - eff.source.sourcePos - ) + else externalCallError(target, eff.source) case _: Cold => theCtx.warning( @@ -313,13 +327,18 @@ object Checking { case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) - if (sym.isInternal) { // tests/init/override17.scala - val cls = sym.owner.asClass - val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, thisRef, cls, Potentials.empty) - } + val target = resolve(cls, sym) + if (target.isInternal) thisRef.potentialsOf(target) else Potentials.empty // warning already issued in call effect + case SuperRef(thisRef @ ThisRef(cls), supercls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolveSuper(cls, supercls, sym) + if (target.isInternal) thisRef.potentialsOf(target) + else Potentials.empty // warning already issued in call effect + + case Fun(pots, effs) => val name = sym.name.toString if (name == "apply") pots @@ -331,12 +350,8 @@ object Checking { else Potentials.empty case warm : Warm => - if (sym.isInternal) { - val cls = sym.owner.asClass - val pots = theEnv.summaryOf(cls).potentialsOf(sym) - val outer = Outer(warm, cls)(warm.source) - Potentials.asSeenFrom(pots, warm, cls, outer.toPots) - } + val target = resolve(warm.classSymbol, sym) + if (target.isInternal) warm.potentialsOf(target) else Potentials.empty // warning already issued in call effect case _: Cold => @@ -353,23 +368,23 @@ object Checking { case thisRef @ ThisRef(cls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) - if (sym.isInternal) { // tests/init/override17.scala - val cls = sym.owner.asClass - val pots = theEnv.summaryOf(cls).potentialsOf(sym) - Potentials.asSeenFrom(pots, thisRef, cls, Potentials.empty) - } + val target = resolve(cls, sym) + if (sym.isInternal) thisRef.potentialsOf(target) + else Cold()(pot.source).toPots + + case SuperRef(thisRef @ ThisRef(cls), supercls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolveSuper(cls, supercls, sym) + if (target.isInternal) thisRef.potentialsOf(target) else Cold()(pot.source).toPots case _: Fun => throw new Exception("Unexpected code reached") case warm: Warm => - if (sym.isInternal) { - val cls = sym.owner.asClass - val pots = theEnv.summaryOf(cls).potentialsOf(sym) - val outer = Outer(warm, cls)(warm.source) - Potentials.asSeenFrom(pots, warm, cls, outer.toPots) - } + val target = resolve(warm.classSymbol, sym) + if (target.isInternal) warm.potentialsOf(target) else Cold()(pot.source).toPots case _: Cold => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index ecdfbfb9a12f..b29e6b65e3e9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -26,6 +26,25 @@ object Potentials { case class ThisRef(classSymbol: ClassSymbol)(val source: Tree) extends Potential { val size: Int = 1 def show(implicit ctx: Context): String = classSymbol.name.show + ".this" + + /** Effects of a method call or a lazy val access + * + * It assumes all the outer `this` are fully initialized. + */ + def effectsOf(sym: Symbol)(implicit env: Env): Effects = { + val cls = sym.owner.asClass + val effs = env.summaryOf(cls).effectsOf(sym) + Effects.asSeenFrom(effs, this, cls, Potentials.empty) + } + + /** Potentials of a field, a method call or a lazy val access + * + */ + def potentialsOf(sym: Symbol)(implicit env: Env): Potentials = { + val cls = sym.owner.asClass + val pots = env.summaryOf(cls).potentialsOf(sym) + Potentials.asSeenFrom(pots, this, cls, Potentials.empty) + } } case class SuperRef(pot: Potential, supercls: ClassSymbol)(val source: Tree) extends Potential { @@ -45,6 +64,28 @@ object Potentials { def size: Int = 1 def show(implicit ctx: Context): String = "Warm[" + classSymbol.show + "]" + /** Effects of a method call or a lazy val access + * + * The method performs prefix and outer substitution + */ + def effectsOf(sym: Symbol)(implicit env: Env): Effects = { + val cls = sym.owner.asClass + val effs = env.summaryOf(cls).effectsOf(sym) + val outer = Outer(this, cls)(this.source) + Effects.asSeenFrom(effs, this, cls, outer.toPots) + } + + /** Potentials of a field, a method call or a lazy val access + * + * The method performs prefix and outer substitution + */ + def potentialsOf(sym: Symbol)(implicit env: Env): Potentials = { + val cls = sym.owner.asClass + val pots = env.summaryOf(cls).potentialsOf(sym) + val outer = Outer(this, cls)(this.source) + Potentials.asSeenFrom(pots, this, cls, outer.toPots) + } + private val outerCache: mutable.Map[ClassSymbol, Potentials] = mutable.Map.empty def outerFor(cls: ClassSymbol)(implicit env: Env): Potentials = if (outerCache.contains(cls)) outerCache(cls) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 0c7f2f687d5c..f33c99b10097 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -20,4 +20,8 @@ object Util { def (symbol: Symbol) isInternal(implicit ctx: Context): Boolean = !symbol.defTree.isEmpty + + def resolve(cls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = ??? + + def resolveSuper(cls: ClassSymbol, superCls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = ??? } \ No newline at end of file From 9f7c853c809f63a8e5330d3b79d63a1f1f9b658e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 00:56:24 +0100 Subject: [PATCH 038/112] Implement resolve and resolveSuper --- .../src/dotty/tools/dotc/transform/init/Checking.scala | 2 +- compiler/src/dotty/tools/dotc/transform/init/Util.scala | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 1c97ae97a462..ed45ac1d5a14 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -102,7 +102,7 @@ object Checking { // check parent calls : follows linearization ordering // see spec 5.1 about "Template Evaluation". - // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#class-linearization + // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html def checkStats(stats: List[Tree])(implicit ctx: Context): Unit = stats.foreach { stat => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index f33c99b10097..a19b18241026 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -21,7 +21,10 @@ object Util { def (symbol: Symbol) isInternal(implicit ctx: Context): Boolean = !symbol.defTree.isEmpty - def resolve(cls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = ??? + def resolve(cls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = + if (sym.isEffectivelyFinal) sym + else sym.matchingMember(cls.typeRef) - def resolveSuper(cls: ClassSymbol, superCls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = ??? + def resolveSuper(cls: ClassSymbol, superCls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = + sym.superSymbolIn(cls) } \ No newline at end of file From ac2b9dc4084599038e22aa6560676386aee1bb35 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 01:11:55 +0100 Subject: [PATCH 039/112] Add checker phase --- .../dotc/transform/init/CheckingPhase.scala | 43 +++++++++++++++++++ .../dotc/transform/init/SetDefTree.scala | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala new file mode 100644 index 000000000000..b428ce3bd227 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala @@ -0,0 +1,43 @@ +package dotty.tools.dotc +package transform +package init + +import MegaPhase._ +import ast.tpd +import core._ +import Contexts.Context + +import scala.collection.mutable + + +class Checker extends MiniPhase { + import tpd._ + + val phaseName = "initChecker" + + // cache of class summary + private val baseEnv = Env(null, mutable.Map.empty) + + override val runsAfter = Set(SetDefTree.name) + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): tpd.Tree = { + if (!tree.isClassDef) return tree + val cls = tree.symbol.asClass + + // A concrete class may not be instantiated if the self type is not satisfied + if (!cls.isOneOf(Flags.AbstractOrTrait)) { + implicit val state = Checking.State( + visited = mutable.Set.empty, + path = Vector.empty, + thisClass = cls, + fieldsInited = mutable.Set.empty, + parentsInited = mutable.Set.empty, + env = baseEnv.withCtx(ctx) + ) + + Checking.checkClassBody(tree) + } + + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala index 564b6b6290ac..ca8fd231f9e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala @@ -11,7 +11,7 @@ class SetDefTree extends MiniPhase { import tpd._ override val phaseName: String = SetDefTree.name - override val runsAfter = Set("flatten", "lambdaLift", Erasure.name) + override val runsAfter = Set(Pickler.name) // don't allow plugins to change tasty // research plugins can still change the phase plan at will From bcd7c586f251c048675ae1b44a04bc9cf7759365 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 01:37:54 +0100 Subject: [PATCH 040/112] Add initialization to compiler phases --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 ++ compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/transform/init/CheckingPhase.scala | 3 +++ compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 8f4ab656881b..5d823641936f 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -59,6 +59,8 @@ class Compiler { new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes new CookComments, // Cook the comments: expand variables, doc, etc. new CheckStatic) :: // Check restrictions that apply to @static members + List(new init.SetDefTree) :: // Attach trees to symbols + List(new init.Checker) :: // Check initialization of objects List(new CompleteJavaEnums, // Fill in constructors for Java enums new ElimRepeated, // Rewrite vararg parameters and arguments new ExpandSAMs, // Expand single abstract method closures to anonymous classes diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f0982ddbb8bc..6e7162b19d54 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -167,6 +167,7 @@ class ScalaSettings extends Settings.SettingGroup { val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Enable kind polymorphism (see https://dotty.epfl.ch/docs/reference/kind-polymorphism.html). Potentially unsound.") val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YerasedTerms: Setting[Boolean] = BooleanSetting("-Yerased-terms", "Allows the use of erased terms.") + val YcheckInit: Setting[Boolean] = BooleanSetting("-Ycheck-init", "Check initialization of objects") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala index b428ce3bd227..79cfff62d7d5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala @@ -20,6 +20,9 @@ class Checker extends MiniPhase { override val runsAfter = Set(SetDefTree.name) + override def isEnabled(implicit ctx: Context): Boolean = + super.isEnabled && ctx.settings.YcheckInit.value + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): tpd.Tree = { if (!tree.isClassDef) return tree val cls = tree.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala index ca8fd231f9e6..4c4b2f480273 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/SetDefTree.scala @@ -13,8 +13,8 @@ class SetDefTree extends MiniPhase { override val phaseName: String = SetDefTree.name override val runsAfter = Set(Pickler.name) - // don't allow plugins to change tasty - // research plugins can still change the phase plan at will + override def isEnabled(implicit ctx: Context): Boolean = + super.isEnabled && ctx.settings.YcheckInit.value override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { val ctx2 = ctx.fresh.setSetting(ctx.settings.YretainTrees, true) From 077eb5b5db23a2f8b1a23b9e97779f1820b865b5 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 01:58:07 +0100 Subject: [PATCH 041/112] First example works --- .../tools/dotc/transform/init/Checking.scala | 46 ++++++++----------- .../dotc/transform/init/Summarization.scala | 2 +- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index ed45ac1d5a14..35ad631288bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -37,7 +37,7 @@ object Checking { parentsInited: mutable.Set[ClassSymbol], env: Env ) { - def withVisited(eff: Effect)(implicit ctx: Context): State = { + def withVisited(eff: Effect): State = { visited += eff copy(path = this.path :+ eff.source) } @@ -49,7 +49,7 @@ object Checking { this.path.foreach { tree => indentCount += 1 val pos = tree.sourcePos - val line = "[ " + pos.source.file.toString + ":" + (pos.line + 1) + " ]" + val line = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" if (last != line) sb.append( if (pos.source.exists) @@ -84,8 +84,8 @@ object Checking { val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs)) if (!vdef.symbol.is(Flags.Lazy)) { - traceIndented(vdef.symbol.show + " initialized", init) checkEffects(effs) + traceIndented(vdef.symbol.show + " initialized", init) state.fieldsInited += vdef.symbol } @@ -168,14 +168,14 @@ object Checking { } } - private def nonInitError(eff: Effect)(implicit state: State) = { + private def nonInitError(field: Symbol)(implicit state: State) = { traceIndented("initialized: " + state.fieldsInited, init) // should issue error, use warning so that it will continue compile subprojects theCtx.warning( - "Access non-initialized field " + eff.source.show + + "Access non-initialized field " + field.show + ". Calling trace:\n" + state.trace, - eff.source.sourcePos + field.sourcePos ) } @@ -189,6 +189,8 @@ object Checking { private def check(eff: Effect)(implicit state: State): Unit = if (!state.visited.contains(eff)) traceOp("checking effect " + eff.show, init) { + implicit val state2: State = state.withVisited(eff) + eff match { case Leak(pot) => pot match { @@ -215,14 +217,12 @@ object Checking { ) case Fun(pots, effs) => - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } - pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + effs.foreach { check(_) } + pots.foreach { pot => check(Leak(pot)(eff.source)) } case pot => - val state2 = state.withVisited(eff) val pots = expand(pot) - pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + pots.foreach { pot => check(Leak(pot)(eff.source)) } } case FieldAccess(pot, field) => @@ -233,13 +233,13 @@ object Checking { assert(cls == state.thisClass, "unexpected potential " + pot.show) val target = resolve(cls, field) - if (!state.fieldsInited.contains(target)) nonInitError(eff) + if (!state.fieldsInited.contains(target)) nonInitError(target) case SuperRef(ThisRef(cls), supercls) => assert(cls == state.thisClass, "unexpected potential " + pot.show) val target = resolveSuper(cls, supercls, field) - if (!state.fieldsInited.contains(target)) nonInitError(eff) + if (!state.fieldsInited.contains(target)) nonInitError(target) case Warm(cls, outer) => // all fields of warm values are initialized @@ -255,9 +255,8 @@ object Checking { throw new Exception("Unexpected effect " + eff.show) case pot => - val state2 = state.withVisited(eff) val pots = expand(pot) - pots.foreach { pot => check(FieldAccess(pot, field)(eff.source))(state2) } + pots.foreach { pot => check(FieldAccess(pot, field)(eff.source)) } } case MethodCall(pot, sym) => @@ -268,8 +267,7 @@ object Checking { val target = resolve(cls, sym) if (target.isInternal) { // tests/init/override17.scala val effs = thisRef.effectsOf(target) - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } + effs.foreach { check(_) } } else externalCallError(target, eff.source) @@ -279,8 +277,7 @@ object Checking { val target = resolveSuper(cls, supercls, sym) if (target.isInternal) { val effs = thisRef.effectsOf(target) - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } + effs.foreach { check(_) } } else externalCallError(target, eff.source) @@ -289,8 +286,7 @@ object Checking { if (target.isInternal) { val effs = warm.effectsOf(target) - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } + effs.foreach { check(_) } } else externalCallError(target, eff.source) @@ -304,17 +300,15 @@ object Checking { case Fun(pots, effs) => // TODO: assertion might be false, due to SAM if (sym.name.toString == "apply") { - val state2 = state.withVisited(eff) - effs.foreach { check(_)(state2) } - pots.foreach { pot => check(Leak(pot)(eff.source))(state2) } + effs.foreach { check(_) } + pots.foreach { pot => check(Leak(pot)(eff.source)) } } // curried, tupled, toString are harmless case pot => - val state2 = state.withVisited(eff) val pots = expand(pot) pots.foreach { pot => - check(MethodCall(pot, sym)(eff.source))(state2) + check(MethodCall(pot, sym)(eff.source)) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index ac29855a5ada..537365f3b024 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -255,7 +255,7 @@ object Summarization { val parentOuter: List[(ClassSymbol, Potentials)] = parents.map { parent => val tref = parent.tpe.typeConstructor.asInstanceOf[TypeRef] - parent.symbol.asClass -> analyze(tref.prefix, parent)._1 + tref.classSymbol.asClass -> analyze(tref.prefix, parent)._1 } ClassSummary(cls, parentOuter.toMap) From 8a9c0cfa6fa5a589c67402a418cbe434093d08c8 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 02:18:37 +0100 Subject: [PATCH 042/112] Handle class parameters Class parameters are always initialized before parent constructor calls and class body fields. --- .../src/dotty/tools/dotc/transform/init/Checking.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 35ad631288bb..c631754864fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -121,6 +121,13 @@ object Checking { ctx.warning("Inheriting non-open class may cause initialization errors", source.sourcePos) } + cls.paramAccessors.foreach { acc => + if (!acc.is(Flags.Method)) { + traceIndented(acc.show + " initialized", init) + state.fieldsInited += acc + } + } + tpl.parents.foreach { case tree @ Block(stats, parent) => val (ctor, _, argss) = decomposeCall(parent) From 25c7a9011d88cafebe4ab70ef494fca7ca22b510 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 03:22:42 +0100 Subject: [PATCH 043/112] Handle ignored methods --- .../tools/dotc/transform/init/Checking.scala | 4 +- .../dotty/tools/dotc/transform/init/Env.scala | 21 ++++++ .../dotc/transform/init/Summarization.scala | 68 +++++++++---------- .../tools/dotc/transform/init/Summary.scala | 6 +- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index c631754864fa..f523e46ac38d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -81,7 +81,7 @@ object Checking { def checkClassBodyStat(tree: Tree)(implicit ctx: Context): Unit = traceOp("checking " + tree.show, init) { tree match { case vdef : ValDef => - val (pots, effs) = Summarization.analyze(vdef.rhs)(ctx.withOwner(vdef.symbol)) + val (pots, effs) = Summarization.analyze(vdef.rhs)(theEnv.withOwner(vdef.symbol)) theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs)) if (!vdef.symbol.is(Flags.Lazy)) { checkEffects(effs) @@ -169,7 +169,7 @@ object Checking { } (stats :+ expr).foreach { stat => - val (_, effs) = Summarization.analyze(stat)(theCtx.withOwner(ctor)) + val (_, effs) = Summarization.analyze(stat)(theEnv.withOwner(ctor)) val rebased = Effects.asSeenFrom(effs, ThisRef(state.thisClass)(null), cls, Potentials.empty) rebased.foreach { check(_) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Env.scala b/compiler/src/dotty/tools/dotc/transform/init/Env.scala index e8a8a1f0883d..51aa7bbbfdb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Env.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -6,6 +6,7 @@ import core._ import ast.Trees._ import ast.tpd import Contexts.Context +import Types._ import Symbols._ import reporting.trace import config.Printers.init @@ -19,8 +20,28 @@ implicit def theCtx(implicit env: Env): Context = env.ctx case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary]) { private implicit def self: Env = this + // Methods that should be ignored in the checking + lazy val ignoredMethods: Set[Symbol] = Set( + // ctx.requiredMethod("scala.runtime.EnumValues.register"), + defn.Any_getClass, + defn.Any_isInstanceOf, + defn.Any_==, + defn.Any_!= + ) + def withCtx(newCtx: Context): Env = this.copy(ctx = newCtx) + def withOwner(owner: Symbol) = this.copy(ctx = this.ctx.withOwner(owner)) + + /** Whether values of a given type is always fully initialized? + * + * It's true for primitive values + */ + def isAlwaysInitialized(tp: Type)(implicit env: Env): Boolean = { + val sym = tp.widen.finalResultType.typeSymbol + sym.isPrimitiveValueClass || sym == defn.StringClass + } + /** Summary of a method or field */ def summaryOf(cls: ClassSymbol): ClassSummary = if (summaryCache.contains(cls)) summaryCache(cls) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 537365f3b024..d4115b3ac522 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -15,17 +15,6 @@ import reporting.trace import Effects._, Potentials._, Summary._, Util._ object Summarization { - private val ignoredMethods = Set( - // "dotty.runtime.LazyVals$.setFlag", - // "dotty.runtime.LazyVals$.get", - // "dotty.runtime.LazyVals$.CAS", - // "dotty.runtime.LazyVals$.wait4Notification", - "scala.runtime.EnumValues.register", - "java.lang.Object.isInstanceOf", - "java.lang.Object.getClass", - "java.lang.Object.eq", - "java.lang.Object.ne" - ) /** Summarization of potentials and effects for an expression * @@ -34,7 +23,7 @@ object Summarization { * 1. potentials for expression of primitive value types can be * safely abandoned, as they are always fully initialized. */ - def analyze(expr: Tree)(implicit ctx: Context): Summary = + def analyze(expr: Tree)(implicit env: Env): Summary = trace("summarizing " + expr.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary: Summary = expr match { case Ident(name) => @@ -49,8 +38,11 @@ object Summarization { case Select(qualifier, name) => val (pots, effs) = analyze(qualifier) - val (pots2, effs2) = pots.select(expr.symbol, expr) - (pots2, effs ++ effs2) + if (env.ignoredMethods.contains(expr.symbol)) (Potentials.empty, effs) + else { + val (pots2, effs2) = pots.select(expr.symbol, expr) + (pots2, effs ++ effs2) + } case _: This => val cls = expr.tpe.widen.classSymbol.asClass @@ -58,11 +50,17 @@ object Summarization { case Apply(fun, args) => val summary = analyze(fun) - args.foldLeft(summary) { (sum, arg) => + val ignoredCall = env.ignoredMethods.contains(expr.symbol) + + val res = args.foldLeft(summary) { (sum, arg) => val (pots1, effs1) = analyze(arg) - sum.withEffs(pots1.leak(arg) ++ effs1) + if (ignoredCall) sum.withEffs(effs1) + else sum.withEffs(pots1.leak(arg) ++ effs1) } + if (ignoredCall) (Potentials.empty, res._2) + else res + case TypeApply(fun, args) => analyze(fun) @@ -178,51 +176,53 @@ object Summarization { throw new Exception("unexpected tree: " + expr.show) } - if (isDefiniteHot(expr.tpe)) (Potentials.empty, summary._2) + if (env.isAlwaysInitialized(expr.tpe)) (Potentials.empty, summary._2) else summary } - private def isDefiniteHot(tp: Type)(implicit ctx: Context): Boolean = { - val sym = tp.widen.finalResultType.typeSymbol - sym.isPrimitiveValueClass || sym == defn.StringClass - } - - def analyze(tp: Type, source: Tree)(implicit ctx: Context): Summary = + def analyze(tp: Type, source: Tree)(implicit env: Env): Summary = trace("summarizing " + tp.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary: Summary = tp match { case tmref: TermRef if tmref.prefix == NoPrefix => Summary.empty + case tmref: TermRef => val (pots, effs) = analyze(tmref.prefix, source) - val (pots2, effs2) = pots.select(tmref.symbol, source) - (pots2, effs ++ effs2) + if (env.ignoredMethods.contains(tmref.symbol)) (Potentials.empty, effs) + else { + val (pots2, effs2) = pots.select(tmref.symbol, source) + (pots2, effs ++ effs2) + } + case ThisType(tref: TypeRef) if tref.classSymbol.is(Flags.Package) => Summary.empty + case thisTp: ThisType => val cls = thisTp.widen.classSymbol.asClass Summary.empty + ThisRef(cls)(source) + case SuperType(thisTp, superTp) => val thisRef = ThisRef(thisTp.classSymbol.asClass)(source) val pot = SuperRef(thisRef, superTp.classSymbol.asClass)(source) Summary.empty + pot } - if (isDefiniteHot(tp)) (Potentials.empty, summary._2) + if (env.isAlwaysInitialized(tp)) (Potentials.empty, summary._2) else summary } - def analyzeMethod(sym: Symbol)(implicit ctx: Context): Summary = { + def analyzeMethod(sym: Symbol)(implicit env: Env): Summary = { val ddef = sym.defTree.asInstanceOf[DefDef] - analyze(ddef.rhs)(ctx.withOwner(sym)) + analyze(ddef.rhs)(env.withOwner(sym)) } - def analyzeField(sym: Symbol)(implicit ctx: Context): Summary = { + def analyzeField(sym: Symbol)(implicit env: Env): Summary = { val vdef = sym.defTree.asInstanceOf[ValDef] - analyze(vdef.rhs)(ctx.withOwner(sym)) + analyze(vdef.rhs)(env.withOwner(sym)) } /** Summarize secondary constructors or class body */ - def analyzeConstructor(ctor: Symbol)(implicit ctx: Context): Summary = + def analyzeConstructor(ctor: Symbol)(implicit env: Env): Summary = trace("summarizing constructor " + ctor.owner.show, init, s => Summary.show(s.asInstanceOf[Summary])) { if (ctor.isPrimaryConstructor) { val tpl = ctor.owner.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] @@ -230,18 +230,18 @@ object Summarization { } else { val ddef = ctor.defTree.asInstanceOf[DefDef] - analyze(ddef.rhs)(ctx.withOwner(ctor)) + analyze(ddef.rhs)(env.withOwner(ctor)) } } - def classSummary(cls: ClassSymbol)(implicit ctx: Context): ClassSummary = + def classSummary(cls: ClassSymbol)(implicit env: Env): ClassSummary = if (cls.defTree.isEmpty) cls.info match { case cinfo: ClassInfo => val parentOuter: List[(ClassSymbol, Potentials)] = cinfo.classParents.map { case parentTp: TypeRef => val source = { - implicit val ctx2: Context = ctx.withSource(cls.source(ctx)) + implicit val ctx2: Context = theCtx.withSource(cls.source(theCtx)) TypeTree(parentTp).withSpan(cls.span) } parentTp.classSymbol.asClass -> analyze(parentTp.prefix, source)._1 diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 2c531baff741..2fa9b45e0b4f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -28,7 +28,7 @@ object Summary { summaryCache(member) = summary } - def summaryOf(member: Symbol)(implicit ctx: Context): Summary = + def summaryOf(member: Symbol)(implicit env: Env): Summary = if (summaryCache.contains(member)) summaryCache(member) else trace("summary for " + member.show, init, s => Summary.show(s.asInstanceOf[Summary])) { val summary = @@ -43,8 +43,8 @@ object Summary { summary } - def effectsOf(member: Symbol)(implicit ctx: Context): Effects = summaryOf(member)._2 - def potentialsOf(member: Symbol)(implicit ctx: Context): Potentials = summaryOf(member)._1 + def effectsOf(member: Symbol)(implicit env: Env): Effects = summaryOf(member)._2 + def potentialsOf(member: Symbol)(implicit env: Env): Potentials = summaryOf(member)._1 def show(implicit ctx: Context): String = "ObjectPart(" + currentClass.name.show + From bf917a7c817debacfa305b64434a46a84b3cbb8b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 04:14:20 +0100 Subject: [PATCH 044/112] Ignore non-instantiable classes --- .../dotc/transform/init/CheckingPhase.scala | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala index 79cfff62d7d5..fb9c98b7762b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/CheckingPhase.scala @@ -2,10 +2,17 @@ package dotty.tools.dotc package transform package init -import MegaPhase._ + +import dotty.tools.dotc._ import ast.tpd -import core._ + +import dotty.tools.dotc.core._ import Contexts.Context +import Types._ + +import dotty.tools.dotc.transform._ +import MegaPhase._ + import scala.collection.mutable @@ -25,10 +32,20 @@ class Checker extends MiniPhase { override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): tpd.Tree = { if (!tree.isClassDef) return tree + val cls = tree.symbol.asClass + val instantiable: Boolean = + cls.is(Flags.Module) || + !cls.isOneOf(Flags.AbstractOrTrait) && { + // see `Checking.checkInstantiable` in typer + val tp = cls.appliedRef + val stp = SkolemType(tp) + val selfType = cls.givenSelfType.asSeenFrom(stp, cls) + !selfType.exists || stp <:< selfType + } // A concrete class may not be instantiated if the self type is not satisfied - if (!cls.isOneOf(Flags.AbstractOrTrait)) { + if (instantiable) { implicit val state = Checking.State( visited = mutable.Set.empty, path = Vector.empty, From 8ba171c15b33c1b5abecdfc26de58a2ff02f43a7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 11:38:59 +0100 Subject: [PATCH 045/112] Handle A & B for this via self type We need to keep all components, as in asSeenFrom all will become empty except the class that contains the code for `this`. --- .../dotty/tools/dotc/transform/init/Effects.scala | 6 ++++-- .../tools/dotc/transform/init/Potentials.scala | 7 +++++-- .../tools/dotc/transform/init/Summarization.scala | 13 +++++++++++-- .../dotty/tools/dotc/transform/init/Summary.scala | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 8d65143a40e7..0a0d4fe4c0b0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -3,6 +3,8 @@ package transform package init import ast.tpd._ +import reporting.trace +import config.Printers.init import core.Types._ import core.Symbols._ import core.Contexts._ @@ -44,7 +46,7 @@ object Effects { def (eff: Effect) toEffs: Effects = Effects.empty + eff def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = - eff match { + trace(eff.show + " asSeenFrom " + thisValue.show + ", outer = " + Potentials.show(outer), init, effs => show(effs.asInstanceOf[Effects])) { eff match { case Leak(pot) => Potentials.asSeenFrom(pot, thisValue, currentClass, outer).leak(eff.source) @@ -57,7 +59,7 @@ object Effects { Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot => MethodCall(pot, sym)(eff.source) } - } + } } def asSeenFrom(effs: Effects, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects = effs.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index b29e6b65e3e9..469266959178 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -5,6 +5,9 @@ package init import scala.collection.mutable import ast.tpd._ +import reporting.trace +import config.Printers.init + import core._ import Types._, Symbols._, Contexts._ @@ -153,7 +156,7 @@ object Potentials { def (ps: Potentials) leak(source: Tree): Effects = ps.map(Leak(_)(source)) def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = - pot match { + trace(pot.show + " asSeenFrom " + thisValue.show + ", outer = " + show(outer), init, pots => show(pots.asInstanceOf[Potentials])) { pot match { case MethodReturn(pot1, sym) => val pots = asSeenFrom(pot1, thisValue, currentClass, outer) pots.map { MethodReturn(_, sym)(pot.source) } @@ -199,7 +202,7 @@ object Potentials { case SuperRef(pot, supercls) => val pots = asSeenFrom(pot, thisValue, currentClass, outer) pots.map { SuperRef(_, supercls)(pot.source) } - } + } } def asSeenFrom(pots: Potentials, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = pots.flatMap(asSeenFrom(_, thisValue, currentClass, outer)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index d4115b3ac522..7d6fb3ae8a2a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -45,8 +45,17 @@ object Summarization { } case _: This => - val cls = expr.tpe.widen.classSymbol.asClass - Summary.empty + ThisRef(cls)(expr) + // With self type, the type can be `A & B`. + def classes(tp: Type): Set[ClassSymbol] = tp.widen match { + case AndType(tp1, tp2) => + classes(tp1) ++ classes(tp2) + + case tp => + Set(tp.classSymbol.asClass) + } + + val pots: Potentials = classes(expr.tpe).map{ ThisRef(_)(expr) } + (pots, Effects.empty) case Apply(fun, args) => val summary = analyze(fun) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 2fa9b45e0b4f..2a0efb8180b5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -47,8 +47,8 @@ object Summary { def potentialsOf(member: Symbol)(implicit env: Env): Potentials = summaryOf(member)._1 def show(implicit ctx: Context): String = - "ObjectPart(" + currentClass.name.show + - "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" + "ClassSummary(" + currentClass.name.show + + ", parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" } /** Part of object. From 99679b847f081e816c8c12415a08b08c8c4a41f0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 11:56:23 +0100 Subject: [PATCH 046/112] Fix classSymbol in parents Previous code gets the constructor symbol instead of the class symbol. --- compiler/src/dotty/tools/dotc/transform/init/Checking.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index f523e46ac38d..f2a4de7500c8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -148,7 +148,7 @@ object Checking { checkCtor(ctor.symbol, parent.tpe, parent) case ref => - val cls = ref.symbol.asClass + val cls = ref.tpe.classSymbol.asClass if (!state.parentsInited.contains(cls)) checkCtor(cls.primaryConstructor, ref.tpe, ref) } From 43d46cd07bf678671ba2e7448b100f4e3ed06249 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 15:23:53 +0100 Subject: [PATCH 047/112] Fix summarization of class In case of local class, the outer is the enclosing class. In checking a class, no need to check args in parent calls, as they are hot. Checking them is subtle, as access of class params are final instead of virtual. Meanwhile, posttyper generates confusing accessor: ``` abstract class Base(val x: Int) class C(x: Int) extends Base(x) ``` After posttyper: ``` abstract class Base(val x: Int) class C(x: Int) extends Base(C.this.x) { private def x: Int = super.x } ``` The accessor `def x: Int = super.x` makes it subtle to give semantics to `Base(C.this.x)`. --- .../tools/dotc/transform/init/Checking.scala | 18 ++++++++++-------- .../dotc/transform/init/Summarization.scala | 14 ++++++++++++-- .../tools/dotc/transform/init/Summary.scala | 4 ++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index f2a4de7500c8..a6148e9964d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -104,10 +104,12 @@ object Checking { // see spec 5.1 about "Template Evaluation". // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html - def checkStats(stats: List[Tree])(implicit ctx: Context): Unit = + def checkParentArgs(stats: List[Tree])(implicit ctx: Context): Unit = stats.foreach { stat => val (_, effs) = Summarization.analyze(stat) - checkEffects(effs) + // access to class parameters are final instead of virtual, thus use `cls` instead of `state.thisClass` + val rebased = Effects.asSeenFrom(effs, ThisRef(cls)(null), cls, Potentials.empty) + rebased.foreach { check(_) } } def checkCtor(ctor: Symbol, tp: Type, source: Tree)(implicit ctx: Context): Unit = { @@ -131,20 +133,20 @@ object Checking { tpl.parents.foreach { case tree @ Block(stats, parent) => val (ctor, _, argss) = decomposeCall(parent) - checkStats(stats) - checkStats(argss.flatten) + // checkParentArgs(stats) + // checkParentArgs(argss.flatten) checkCtor(ctor.symbol, parent.tpe, tree) case tree @ Apply(Block(stats, parent), args) => val (ctor, _, argss) = decomposeCall(parent) - checkStats(stats) - checkStats(args) - checkStats(argss.flatten) + // checkParentArgs(stats) + // checkParentArgs(args) + // checkParentArgs(argss.flatten) checkCtor(ctor.symbol, tree.tpe, tree) case parent : Apply => val (ctor, _, argss) = decomposeCall(parent) - checkStats(argss.flatten) + // checkParentArgs(argss.flatten) checkCtor(ctor.symbol, parent.tpe, parent) case ref => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 7d6fb3ae8a2a..1502b57e27af 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -222,6 +222,7 @@ object Summarization { def analyzeMethod(sym: Symbol)(implicit env: Env): Summary = { val ddef = sym.defTree.asInstanceOf[DefDef] + traceIndented(sym.show + " = " + ddef.show, init) analyze(ddef.rhs)(env.withOwner(sym)) } @@ -253,7 +254,11 @@ object Summarization { implicit val ctx2: Context = theCtx.withSource(cls.source(theCtx)) TypeTree(parentTp).withSpan(cls.span) } - parentTp.classSymbol.asClass -> analyze(parentTp.prefix, source)._1 + val parentCls = parentTp.classSymbol.asClass + if (parentTp.prefix != NoPrefix) + parentCls -> analyze(parentTp.prefix, source)._1 + else + parentCls -> analyze(cls.enclosingClass.thisType, source)._1 } ClassSummary(cls, parentOuter.toMap) @@ -264,7 +269,12 @@ object Summarization { val parentOuter: List[(ClassSymbol, Potentials)] = parents.map { parent => val tref = parent.tpe.typeConstructor.asInstanceOf[TypeRef] - tref.classSymbol.asClass -> analyze(tref.prefix, parent)._1 + val parentCls = tref.classSymbol.asClass + if (tref.prefix != NoPrefix) + parentCls ->analyze(tref.prefix, parent)._1 + else + parentCls -> analyze(cls.enclosingClass.thisType, parent)._1 + } ClassSummary(cls, parentOuter.toMap) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index 2a0efb8180b5..59a966cc0051 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -48,7 +48,7 @@ object Summary { def show(implicit ctx: Context): String = "ClassSummary(" + currentClass.name.show + - ", parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" + ", parents = " + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } } /** Part of object. @@ -76,7 +76,7 @@ object Summary { def show(implicit ctx: Context): String = "ObjectPart(this = " + thisValue.show + "," + currentClass.name.show + ", outer = " + Potentials.show(currentOuter) + - "parents = {" + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } + "}" + "parents = " + parentOuter.map { case (k, v) => k.show + "->" + "[" + Potentials.show(v) + "]" } } def show(summary: Summary)(implicit ctx: Context): String = { From 9b99ffc4b6fd2bda2044dec20875ed0531648511 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 15:39:56 +0100 Subject: [PATCH 048/112] A method call might be a field access --- compiler/src/dotty/tools/dotc/transform/init/Checking.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index a6148e9964d0..ba74923fe8db 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -274,7 +274,9 @@ object Checking { assert(cls == state.thisClass, "unexpected potential " + pot.show) val target = resolve(cls, sym) - if (target.isInternal) { // tests/init/override17.scala + if (!target.is(Flags.Method)) + check(FieldAccess(pot, target)(eff.source)) + else if (target.isInternal) { val effs = thisRef.effectsOf(target) effs.foreach { check(_) } } @@ -284,6 +286,8 @@ object Checking { assert(cls == state.thisClass, "unexpected potential " + pot.show) val target = resolveSuper(cls, supercls, sym) + if (!target.is(Flags.Method)) + check(FieldAccess(pot, target)(eff.source)) if (target.isInternal) { val effs = thisRef.effectsOf(target) effs.foreach { check(_) } From ec03ffd9820ab68457fc73a6fa996fce49c59d5f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sun, 15 Dec 2019 16:25:55 +0100 Subject: [PATCH 049/112] Parent child tests are green --- .../dotty/tools/dotc/CompilationTests.scala | 17 + tests/init/neg/crash/explicitOuter.scala | 62 ++ tests/init/neg/crash/explicitOuter.scala.out | 6 + tests/init/neg/crash/final-fields.scala | 43 ++ tests/init/neg/crash/fors.scala | 117 ++++ tests/init/neg/crash/fors.scala.out | 7 + tests/init/neg/crash/i1990b.scala | 20 + tests/init/neg/crash/i2468.scala | 27 + tests/init/neg/crash/i5606.scala | 14 + tests/init/neg/crash/i5606.scala.out | 6 + tests/init/neg/crash/i6109.scala | 64 ++ tests/init/neg/crash/i6109.scala.out | 6 + tests/init/neg/crash/i6858.scala | 10 + tests/init/neg/crash/i6858.scala.out | 7 + tests/init/neg/crash/i6914.scala | 29 + tests/init/neg/crash/opassign.scala | 28 + tests/init/neg/crash/private-leak.scala | 9 + tests/init/neg/crash/rbtree.scala | 565 ++++++++++++++++++ tests/init/neg/crash/t0055.scala | 6 + tests/init/neg/crash/t10032.scala | 164 +++++ tests/init/neg/crash/t10032.scala.out | 6 + tests/init/neg/crash/t2111.scala | 20 + tests/init/neg/crash/t2111.scala.out | 6 + tests/init/neg/crash/t7120/Base_1.scala | 10 + tests/init/neg/crash/t7120/Derived_2.scala | 9 + tests/init/neg/crash/t7120/Run_3.scala | 3 + tests/init/neg/crash/t8002.scala | 19 + tests/init/neg/crash/t8002.scala.out | 6 + tests/init/neg/crash/tuples.scala | 31 + tests/init/neg/crash/tuples.scala.out | 12 + tests/init/neg/crash/virtpatmat_alts.scala | 15 + .../neg/features/billion-dollar-fix.scala | 34 ++ tests/init/neg/features/doublelist.scala | 6 + tests/init/neg/features/even.scala | 5 + tests/init/neg/features/high-order.scala | 9 + tests/init/neg/features/linearization.scala | 10 + tests/init/neg/features/scalacheck.scala | 16 + tests/init/neg/features/trees.scala | 6 + tests/init/neg/freedom/field-usage.scala | 14 + tests/init/neg/freedom/method-usage.scala | 4 + tests/init/neg/freedom/outer.scala.out | 5 + .../freedom/tolerant-boundary/child_2.scala | 3 + .../freedom/tolerant-boundary/parent_1.scala | 4 + tests/init/neg/functions/even.scala | 5 + tests/init/neg/functions/function1.scala | 12 + tests/init/neg/functions/function10.scala | 7 + tests/init/neg/functions/function11.scala | 33 + tests/init/neg/functions/function2.scala | 6 + tests/init/neg/functions/high-order.scala | 9 + tests/init/neg/hybrid/hybrid1.scala | 21 + tests/init/neg/hybrid/hybrid2.scala | 18 + tests/init/neg/hybrid/hybrid4.scala | 12 + tests/init/neg/hybrid/hybrid5.scala | 19 + tests/init/neg/hybrid/hybrid6.scala | 15 + tests/init/neg/inner-outer/inner-case.scala | 11 + tests/init/neg/inner-outer/inner-loop.scala | 15 + tests/init/neg/inner-outer/inner-new.scala | 11 + tests/init/neg/inner-outer/inner1.scala | 18 + tests/init/neg/inner-outer/inner11.scala | 35 ++ tests/init/neg/inner-outer/inner13.scala | 10 + tests/init/neg/inner-outer/inner15.scala | 19 + tests/init/neg/inner-outer/inner16.scala | 17 + tests/init/neg/inner-outer/inner17.scala | 13 + tests/init/neg/inner-outer/inner19.scala | 19 + tests/init/neg/inner-outer/inner20.scala | 18 + tests/init/neg/inner-outer/inner21.scala | 26 + tests/init/neg/inner-outer/inner22.scala | 32 + tests/init/neg/inner-outer/inner23.scala | 13 + tests/init/neg/inner-outer/inner24.scala | 18 + tests/init/neg/inner-outer/inner25.scala | 14 + tests/init/neg/inner-outer/inner28.scala | 21 + tests/init/neg/inner-outer/inner29.scala | 21 + tests/init/neg/inner-outer/inner4.scala | 10 + tests/init/neg/inner-outer/inner5.scala | 10 + tests/init/neg/inner-outer/inner6.scala | 23 + tests/init/neg/inner-outer/inner7.scala | 16 + tests/init/neg/inner-outer/inner9.scala | 20 + tests/init/neg/inner-outer/trees.scala | 7 + tests/init/neg/misc/Properties.scala | 99 +++ tests/init/neg/misc/alias.scala | 5 + tests/init/neg/misc/assignments.scala | 26 + tests/init/neg/misc/by-name-error.scala | 13 + tests/init/neg/misc/by-name-inline.scala | 13 + tests/init/neg/misc/constant.scala | 4 + tests/init/neg/misc/enum.scala | 3 + tests/init/neg/misc/escape1.scala | 8 + tests/init/neg/misc/exception1.scala | 14 + tests/init/neg/misc/explosion.scala | 379 ++++++++++++ tests/init/neg/misc/flow2.scala | 7 + tests/init/neg/misc/inner-pat_iuli.scala | 24 + tests/init/neg/misc/lazy.scala | 11 + tests/init/neg/misc/lazylist1.scala | 27 + tests/init/neg/misc/lazylist2.scala | 51 ++ tests/init/neg/misc/llift.scala | 156 +++++ tests/init/neg/misc/recursive-new.scala | 9 + tests/init/neg/misc/second-ctor.scala | 10 + tests/init/neg/misc/simple1.scala | 4 + tests/init/neg/misc/simple2.scala | 20 + tests/init/neg/misc/simple3.scala | 5 + tests/init/neg/misc/simple5.scala | 21 + tests/init/neg/misc/synchronized.scala | 9 + .../init/neg/parent-child/AbstractFile.scala | 9 + tests/init/neg/parent-child/override1.scala | 20 + tests/init/neg/parent-child/override10.scala | 9 + tests/init/neg/parent-child/override13.scala | 13 + tests/init/neg/parent-child/override14.scala | 10 + tests/init/neg/parent-child/override15.scala | 11 + tests/init/neg/parent-child/override16.scala | 23 + tests/init/neg/parent-child/override17.scala | 9 + tests/init/neg/parent-child/override18.scala | 23 + tests/init/neg/parent-child/override19.scala | 10 + tests/init/neg/parent-child/override2.scala | 27 + tests/init/neg/parent-child/override20.scala | 9 + .../neg/parent-child/override21.scala.bak | 16 + .../neg/parent-child/override22.scala.bak | 16 + .../neg/parent-child/override23.scala.bak | 16 + tests/init/neg/parent-child/override24.scala | 21 + tests/init/neg/parent-child/override25.scala | 17 + tests/init/neg/parent-child/override26.scala | 16 + tests/init/neg/parent-child/override27.scala | 17 + tests/init/neg/parent-child/override28.scala | 10 + tests/init/neg/parent-child/override29.scala | 11 + tests/init/neg/parent-child/override3.scala | 29 + tests/init/neg/parent-child/override30.scala | 15 + tests/init/neg/parent-child/override31.scala | 15 + tests/init/neg/parent-child/override32.scala | 15 + tests/init/neg/parent-child/override33.scala | 18 + tests/init/neg/parent-child/override34.scala | 13 + tests/init/neg/parent-child/override35.scala | 15 + tests/init/neg/parent-child/override36.scala | 18 + tests/init/neg/parent-child/override38.scala | 14 + tests/init/neg/parent-child/override39.scala | 11 + tests/init/neg/parent-child/override4.scala | 20 + tests/init/neg/parent-child/override40.scala | 10 + tests/init/neg/parent-child/override41.scala | 15 + tests/init/neg/parent-child/override42.scala | 16 + tests/init/neg/parent-child/override43.scala | 13 + tests/init/neg/parent-child/override44.scala | 13 + .../neg/parent-child/override45.scala.bak | 12 + tests/init/neg/parent-child/override46.scala | 9 + tests/init/neg/parent-child/override5.scala | 33 + tests/init/neg/parent-child/override6.scala | 8 + tests/init/neg/parent-child/override7.scala | 21 + tests/init/neg/parent-child/override8.scala | 35 ++ tests/init/neg/parent-child/override9.scala | 8 + tests/init/neg/parent-child/private.scala | 15 + tests/init/neg/soundness/soundness1.scala | 7 + tests/init/neg/soundness/soundness2.scala | 3 + tests/init/neg/soundness/soundness3.scala | 4 + tests/init/neg/soundness/soundness4.scala | 3 + tests/init/neg/soundness/soundness5.scala | 5 + tests/init/neg/soundness/soundness6.scala | 5 + tests/init/pos/parent-child/nullary.scala | 20 + 153 files changed, 3703 insertions(+) create mode 100644 tests/init/neg/crash/explicitOuter.scala create mode 100644 tests/init/neg/crash/explicitOuter.scala.out create mode 100644 tests/init/neg/crash/final-fields.scala create mode 100644 tests/init/neg/crash/fors.scala create mode 100644 tests/init/neg/crash/fors.scala.out create mode 100644 tests/init/neg/crash/i1990b.scala create mode 100644 tests/init/neg/crash/i2468.scala create mode 100644 tests/init/neg/crash/i5606.scala create mode 100644 tests/init/neg/crash/i5606.scala.out create mode 100644 tests/init/neg/crash/i6109.scala create mode 100644 tests/init/neg/crash/i6109.scala.out create mode 100644 tests/init/neg/crash/i6858.scala create mode 100644 tests/init/neg/crash/i6858.scala.out create mode 100644 tests/init/neg/crash/i6914.scala create mode 100644 tests/init/neg/crash/opassign.scala create mode 100644 tests/init/neg/crash/private-leak.scala create mode 100644 tests/init/neg/crash/rbtree.scala create mode 100644 tests/init/neg/crash/t0055.scala create mode 100644 tests/init/neg/crash/t10032.scala create mode 100644 tests/init/neg/crash/t10032.scala.out create mode 100644 tests/init/neg/crash/t2111.scala create mode 100644 tests/init/neg/crash/t2111.scala.out create mode 100644 tests/init/neg/crash/t7120/Base_1.scala create mode 100644 tests/init/neg/crash/t7120/Derived_2.scala create mode 100644 tests/init/neg/crash/t7120/Run_3.scala create mode 100644 tests/init/neg/crash/t8002.scala create mode 100644 tests/init/neg/crash/t8002.scala.out create mode 100644 tests/init/neg/crash/tuples.scala create mode 100644 tests/init/neg/crash/tuples.scala.out create mode 100644 tests/init/neg/crash/virtpatmat_alts.scala create mode 100644 tests/init/neg/features/billion-dollar-fix.scala create mode 100644 tests/init/neg/features/doublelist.scala create mode 100644 tests/init/neg/features/even.scala create mode 100644 tests/init/neg/features/high-order.scala create mode 100644 tests/init/neg/features/linearization.scala create mode 100644 tests/init/neg/features/scalacheck.scala create mode 100644 tests/init/neg/features/trees.scala create mode 100644 tests/init/neg/freedom/field-usage.scala create mode 100644 tests/init/neg/freedom/method-usage.scala create mode 100644 tests/init/neg/freedom/outer.scala.out create mode 100644 tests/init/neg/freedom/tolerant-boundary/child_2.scala create mode 100644 tests/init/neg/freedom/tolerant-boundary/parent_1.scala create mode 100644 tests/init/neg/functions/even.scala create mode 100644 tests/init/neg/functions/function1.scala create mode 100644 tests/init/neg/functions/function10.scala create mode 100644 tests/init/neg/functions/function11.scala create mode 100644 tests/init/neg/functions/function2.scala create mode 100644 tests/init/neg/functions/high-order.scala create mode 100644 tests/init/neg/hybrid/hybrid1.scala create mode 100644 tests/init/neg/hybrid/hybrid2.scala create mode 100644 tests/init/neg/hybrid/hybrid4.scala create mode 100644 tests/init/neg/hybrid/hybrid5.scala create mode 100644 tests/init/neg/hybrid/hybrid6.scala create mode 100644 tests/init/neg/inner-outer/inner-case.scala create mode 100644 tests/init/neg/inner-outer/inner-loop.scala create mode 100644 tests/init/neg/inner-outer/inner-new.scala create mode 100644 tests/init/neg/inner-outer/inner1.scala create mode 100644 tests/init/neg/inner-outer/inner11.scala create mode 100644 tests/init/neg/inner-outer/inner13.scala create mode 100644 tests/init/neg/inner-outer/inner15.scala create mode 100644 tests/init/neg/inner-outer/inner16.scala create mode 100644 tests/init/neg/inner-outer/inner17.scala create mode 100644 tests/init/neg/inner-outer/inner19.scala create mode 100644 tests/init/neg/inner-outer/inner20.scala create mode 100644 tests/init/neg/inner-outer/inner21.scala create mode 100644 tests/init/neg/inner-outer/inner22.scala create mode 100644 tests/init/neg/inner-outer/inner23.scala create mode 100644 tests/init/neg/inner-outer/inner24.scala create mode 100644 tests/init/neg/inner-outer/inner25.scala create mode 100644 tests/init/neg/inner-outer/inner28.scala create mode 100644 tests/init/neg/inner-outer/inner29.scala create mode 100644 tests/init/neg/inner-outer/inner4.scala create mode 100644 tests/init/neg/inner-outer/inner5.scala create mode 100644 tests/init/neg/inner-outer/inner6.scala create mode 100644 tests/init/neg/inner-outer/inner7.scala create mode 100644 tests/init/neg/inner-outer/inner9.scala create mode 100644 tests/init/neg/inner-outer/trees.scala create mode 100644 tests/init/neg/misc/Properties.scala create mode 100644 tests/init/neg/misc/alias.scala create mode 100644 tests/init/neg/misc/assignments.scala create mode 100644 tests/init/neg/misc/by-name-error.scala create mode 100644 tests/init/neg/misc/by-name-inline.scala create mode 100644 tests/init/neg/misc/constant.scala create mode 100644 tests/init/neg/misc/enum.scala create mode 100644 tests/init/neg/misc/escape1.scala create mode 100644 tests/init/neg/misc/exception1.scala create mode 100644 tests/init/neg/misc/explosion.scala create mode 100644 tests/init/neg/misc/flow2.scala create mode 100644 tests/init/neg/misc/inner-pat_iuli.scala create mode 100644 tests/init/neg/misc/lazy.scala create mode 100644 tests/init/neg/misc/lazylist1.scala create mode 100644 tests/init/neg/misc/lazylist2.scala create mode 100644 tests/init/neg/misc/llift.scala create mode 100644 tests/init/neg/misc/recursive-new.scala create mode 100644 tests/init/neg/misc/second-ctor.scala create mode 100644 tests/init/neg/misc/simple1.scala create mode 100644 tests/init/neg/misc/simple2.scala create mode 100644 tests/init/neg/misc/simple3.scala create mode 100644 tests/init/neg/misc/simple5.scala create mode 100644 tests/init/neg/misc/synchronized.scala create mode 100644 tests/init/neg/parent-child/AbstractFile.scala create mode 100644 tests/init/neg/parent-child/override1.scala create mode 100644 tests/init/neg/parent-child/override10.scala create mode 100644 tests/init/neg/parent-child/override13.scala create mode 100644 tests/init/neg/parent-child/override14.scala create mode 100644 tests/init/neg/parent-child/override15.scala create mode 100644 tests/init/neg/parent-child/override16.scala create mode 100644 tests/init/neg/parent-child/override17.scala create mode 100644 tests/init/neg/parent-child/override18.scala create mode 100644 tests/init/neg/parent-child/override19.scala create mode 100644 tests/init/neg/parent-child/override2.scala create mode 100644 tests/init/neg/parent-child/override20.scala create mode 100644 tests/init/neg/parent-child/override21.scala.bak create mode 100644 tests/init/neg/parent-child/override22.scala.bak create mode 100644 tests/init/neg/parent-child/override23.scala.bak create mode 100644 tests/init/neg/parent-child/override24.scala create mode 100644 tests/init/neg/parent-child/override25.scala create mode 100644 tests/init/neg/parent-child/override26.scala create mode 100644 tests/init/neg/parent-child/override27.scala create mode 100644 tests/init/neg/parent-child/override28.scala create mode 100644 tests/init/neg/parent-child/override29.scala create mode 100644 tests/init/neg/parent-child/override3.scala create mode 100644 tests/init/neg/parent-child/override30.scala create mode 100644 tests/init/neg/parent-child/override31.scala create mode 100644 tests/init/neg/parent-child/override32.scala create mode 100644 tests/init/neg/parent-child/override33.scala create mode 100644 tests/init/neg/parent-child/override34.scala create mode 100644 tests/init/neg/parent-child/override35.scala create mode 100644 tests/init/neg/parent-child/override36.scala create mode 100644 tests/init/neg/parent-child/override38.scala create mode 100644 tests/init/neg/parent-child/override39.scala create mode 100644 tests/init/neg/parent-child/override4.scala create mode 100644 tests/init/neg/parent-child/override40.scala create mode 100644 tests/init/neg/parent-child/override41.scala create mode 100644 tests/init/neg/parent-child/override42.scala create mode 100644 tests/init/neg/parent-child/override43.scala create mode 100644 tests/init/neg/parent-child/override44.scala create mode 100644 tests/init/neg/parent-child/override45.scala.bak create mode 100644 tests/init/neg/parent-child/override46.scala create mode 100644 tests/init/neg/parent-child/override5.scala create mode 100644 tests/init/neg/parent-child/override6.scala create mode 100644 tests/init/neg/parent-child/override7.scala create mode 100644 tests/init/neg/parent-child/override8.scala create mode 100644 tests/init/neg/parent-child/override9.scala create mode 100644 tests/init/neg/parent-child/private.scala create mode 100644 tests/init/neg/soundness/soundness1.scala create mode 100644 tests/init/neg/soundness/soundness2.scala create mode 100644 tests/init/neg/soundness/soundness3.scala create mode 100644 tests/init/neg/soundness/soundness4.scala create mode 100644 tests/init/neg/soundness/soundness5.scala create mode 100644 tests/init/neg/soundness/soundness6.scala create mode 100644 tests/init/pos/parent-child/nullary.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 501f57c0049b..805942aa3eb1 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -294,6 +294,23 @@ class CompilationTests extends ParallelTesting { implicit val testGroup: TestGroup = TestGroup("explicitNullsRun") compileFilesInDir("tests/explicit-nulls/run", explicitNullsOptions) }.checkRuns() + + // initialization tests + @Test def checkInitNeg: Unit = { + implicit val testGroup: TestGroup = TestGroup("checkInit") + val options = defaultOptions.and("-Ycheck-init", "-Xfatal-warnings") + aggregateTests( + compileFilesInDir("tests/init/neg/parent-child", options), + compileFilesInDir("tests/init/neg/inner-outer", options), + compileFilesInDir("tests/init/neg/soundness", options), + compileFilesInDir("tests/init/neg/features", options), + compileFilesInDir("tests/init/neg/misc", options), + compileFilesInDir("tests/init/neg/functions", options), + compileFilesInDir("tests/init/neg/hybrid", options), + compileFilesInDir("tests/init/neg/crash", options), + ) + }.checkExpectedErrors() + } object CompilationTests { diff --git a/tests/init/neg/crash/explicitOuter.scala b/tests/init/neg/crash/explicitOuter.scala new file mode 100644 index 000000000000..44b441956420 --- /dev/null +++ b/tests/init/neg/crash/explicitOuter.scala @@ -0,0 +1,62 @@ +class Outer(elem: Int, val next: Outer) { + + trait InnerTrait { + def foo = elem + } + + class InnerClass(x: Int) extends next.InnerTrait { + def this() = this(3) + def bar = elem + x + } + + class EmptyInnerClass { + def foo = 1 // still needs outer because it is not private + } + + def inner = { + trait InnerTrait { + def foo = elem + } + + class InnerClass(x: Int) extends next.InnerTrait { + def this() = this(3) + def bar = elem + x + } + + class EmptyInnerClass { + def foo = 1 // does not need outer + } + + val ic = new InnerClass(1) + println(ic.bar) + println(ic.foo) + val it = new InnerTrait {} + println(it.foo) + val ec = new EmptyInnerClass + } + + def inner2 = { + class C { + val x = elem + } + class D { + new C + } + class E { + f() + } + def f() = () + } +} + +object Test extends App { + + val o = new Outer(1, new Outer(2, null)) + val ic = new o.InnerClass(1) + println(ic.bar) + println(ic.foo) + val it = new o.InnerTrait {} + println(it.foo) + val ec = new o.EmptyInnerClass + o.inner +} diff --git a/tests/init/neg/crash/explicitOuter.scala.out b/tests/init/neg/crash/explicitOuter.scala.out new file mode 100644 index 000000000000..5c6d987a77aa --- /dev/null +++ b/tests/init/neg/crash/explicitOuter.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/explicitOuter.scala:52:7 -------------------------------- +52 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/explicitOuter.scala:52 ] +one error found diff --git a/tests/init/neg/crash/final-fields.scala b/tests/init/neg/crash/final-fields.scala new file mode 100644 index 000000000000..cb53fecc2931 --- /dev/null +++ b/tests/init/neg/crash/final-fields.scala @@ -0,0 +1,43 @@ +trait T { + + val f1: Int = {println("T.f1"); -1} + val f2: Int = {println("T.f2"); -2} + val f3: Int = {println("T.f3"); -3} + val f4: Int = {println("T.f4"); -4} + + println(s"$f1 $f2 $f3 $f4") +} + +trait U { + val f2: Int +} + +object Test0 extends U { + final val f1 = 1 + final val f2 = 2 + final val f3 = f1 + f2 + val f4: 3 = f3 +} + +object Test1 extends U { + final val f1 = 1 + final val f3 = f1 + f2 + final val f2 = 2 + val f4: 3 = f3 + + +} + +object Test extends T { + override final val f1 = /*super.f1*/ 1 + f2 + override final val f2 = 2 + override final val f3 = {println(3); 3} // error + override val f4 = f3 + 1 // error + + def g: 3 = { println("g"); 3 } + final val x = g + 1 + def main(args: Array[String]): Unit = { + Test0 + Test1 + } +} diff --git a/tests/init/neg/crash/fors.scala b/tests/init/neg/crash/fors.scala new file mode 100644 index 000000000000..d8f76473f773 --- /dev/null +++ b/tests/init/neg/crash/fors.scala @@ -0,0 +1,117 @@ +//############################################################################ +// for-comprehensions (old and new syntax) +//############################################################################ + +//############################################################################ + +object Test extends App { + val xs = List(1, 2, 3) + val ys = List(Symbol("a"), Symbol("b"), Symbol("c")) + + def it = 0 until 10 + + val ar = "abc".toCharArray + + /////////////////// old syntax /////////////////// + + def testOld(): Unit = { + println("\ntestOld") + + // lists + for (x <- xs) print(x + " "); println() + for (x <- xs; + if x % 2 == 0) print(x + " "); println() + for {x <- xs + if x % 2 == 0} print(x + " "); println() + var n = 0 + for (_ <- xs) n += 1; println(n) + for ((x, y) <- xs zip ys) print(x + " "); println() + for (p @ (x, y) <- xs zip ys) print(p._1 + " "); println() + + // iterators + for (x <- it) print(x + " "); println() + for (x <- it; + if x % 2 == 0) print(x + " "); println() + for {x <- it + if x % 2 == 0} print(x + " "); println() + + // arrays + for (x <- ar) print(x + " "); println() + for (x <- ar; + if x.toInt > 97) print(x + " "); println() + for {x <- ar + if x.toInt > 97} print(x + " "); println() + + } + + /////////////////// new syntax /////////////////// + + def testNew(): Unit = { + println("\ntestNew") + + // lists + var n = 0 + for (_ <- xs) n += 1; println(n) + for ((x, y) <- xs zip ys) print(x + " "); println() + for (p @ (x, y) <- xs zip ys) print(p._1 + " "); println() + + // iterators + for (x <- it) print(x + " "); println() + for (x <- it if x % 2 == 0) print(x + " "); println() + for (x <- it; if x % 2 == 0) print(x + " "); println() + for (x <- it; + if x % 2 == 0) print(x + " "); println() + for (x <- it + if x % 2 == 0) print(x + " "); println() + for {x <- it + if x % 2 == 0} print(x + " "); println() + for (x <- it; + y = 2 + if x % y == 0) print(x + " "); println() + for {x <- it + y = 2 + if x % y == 0} print(x + " "); println() + + // arrays + for (x <- ar) print(x + " "); println() + + } + + /////////////////// filtering with case /////////////////// + + def testFiltering(): Unit = { + println("\ntestFiltering") + + val xs: List[Any] = List((1, 2), "hello", (3, 4), "", "world") + + for (case x: String <- xs) do print(s"$x "); println() + for (case (x: String) <- xs) do print(s"$x "); println() + for (case y@ (x: String) <- xs) do print(s"$y "); println() + + for (case (x, y) <- xs) do print(s"$x~$y "); println() + + for (case (x: String) <- xs if x.isEmpty) do print("(empty)"); println() + for (case (x: String) <- xs; y = x) do print(s"$y "); println() + for (case (x: String) <- xs; case (y, z) <- xs) do print(s"$x/$y~$z "); println() + + for (case (x, y) <- xs) do print(s"${(y, x)} "); println() + + for case x: String <- xs do print(s"$x "); println() + for case (x: String) <- xs do print(s"$x "); println() + for case y@ (x: String) <- xs do print(s"$y "); println() + + for case (x, y) <- xs do print(s"$x~$y "); println() + + for case (x: String) <- xs if x.isEmpty do print("(empty)"); println() + for case (x: String) <- xs; y = x do print(s"$y "); println() + for case (x: String) <- xs; case (y, z) <- xs do print(s"$x/$y~$z "); println() + + for case (x, y) <- xs do print(s"${(y, x)} "); println() + } + + //////////////////////////////////////////////////// + + testOld() + testNew() + testFiltering() +} diff --git a/tests/init/neg/crash/fors.scala.out b/tests/init/neg/crash/fors.scala.out new file mode 100644 index 000000000000..0fd3e66c70ce --- /dev/null +++ b/tests/init/neg/crash/fors.scala.out @@ -0,0 +1,7 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/fors.scala:7:7 ------------------------------------------ +7 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/fors.scala:7 ] +one error found +there were 22 deprecation warning(s); re-run with -deprecation for details diff --git a/tests/init/neg/crash/i1990b.scala b/tests/init/neg/crash/i1990b.scala new file mode 100644 index 000000000000..1b1ac932ec2c --- /dev/null +++ b/tests/init/neg/crash/i1990b.scala @@ -0,0 +1,20 @@ +trait A { self => + class Foo { + inline def inlineMeth: Unit = { + println(self) + } + } +} + +case class A2() extends A { + case class C() extends Foo with A + + val c = new C + (new c.Foo).inlineMeth // error +} + +object Test { + def main(args: Array[String]): Unit = { + new A2 + } +} diff --git a/tests/init/neg/crash/i2468.scala b/tests/init/neg/crash/i2468.scala new file mode 100644 index 000000000000..801d92ea6fa6 --- /dev/null +++ b/tests/init/neg/crash/i2468.scala @@ -0,0 +1,27 @@ + +object Test { + + class A { + private[this] var x: String = _ + } + + class B { + private[this] var x: String = "good" + x = "foo" + } + + class C { + private[this] var x1: Int = _ + private[this] var x2: Unit = _ + private[this] var x3: Char = _ + private[this] var x4: Boolean = _ + private[this] var x5: Float = _ + private[this] var x6: Double = _ + private[this] var x7: Char = _ + private[this] var x8: Byte = _ + private[this] var x9: AnyVal = _ + private[this] var x10: D = _ + } + + class D(x: Int) extends AnyVal +} diff --git a/tests/init/neg/crash/i5606.scala b/tests/init/neg/crash/i5606.scala new file mode 100644 index 000000000000..a7b9fc340b60 --- /dev/null +++ b/tests/init/neg/crash/i5606.scala @@ -0,0 +1,14 @@ +object Test extends App { + + def (f: A => B) `$`[A, B](a: A): B = f(a) + + assert((((a: Int) => a.toString()) `$` 10) == "10") + + def g(x: Int): String = x.toString + + assert((g `$` 10) == "10") + + val h: Int => String = _.toString + + assert((h `$` 10) == "10") +} diff --git a/tests/init/neg/crash/i5606.scala.out b/tests/init/neg/crash/i5606.scala.out new file mode 100644 index 000000000000..6e852019c12a --- /dev/null +++ b/tests/init/neg/crash/i5606.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/i5606.scala:1:7 ----------------------------------------- +1 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/i5606.scala:1 ] +one error found diff --git a/tests/init/neg/crash/i6109.scala b/tests/init/neg/crash/i6109.scala new file mode 100644 index 000000000000..30093fa05242 --- /dev/null +++ b/tests/init/neg/crash/i6109.scala @@ -0,0 +1,64 @@ +object Test extends App { + val f2 = (x1: Int, x2: Int) => x2 + assert(f2.curried(1)(2) == 2) + + val f3 = (x1: Int, x2: Int, x3: Int) => x3 + assert(f3.curried(1)(2)(3) == 3) + + val f4 = (x1: Int, x2: Int, x3: Int, x4: Int) => x4 + assert(f4.curried(1)(2)(3)(4) == 4) + + val f5 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int) => x5 + assert(f5.curried(1)(2)(3)(4)(5) == 5) + + val f6 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int) => x6 + assert(f6.curried(1)(2)(3)(4)(5)(6) == 6) + + val f7 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int) => x7 + assert(f7.curried(1)(2)(3)(4)(5)(6)(7) == 7) + + val f8 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int) => x8 + assert(f8.curried(1)(2)(3)(4)(5)(6)(7)(8) == 8) + + val f9 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int) => x9 + assert(f9.curried(1)(2)(3)(4)(5)(6)(7)(8)(9) == 9) + + val f10 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int) => x10 + assert(f10.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) == 10) + + val f11 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int) => x11 + assert(f11.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11) == 11) + + val f12 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int) => x12 + assert(f12.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12) == 12) + + val f13 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int) => x13 + assert(f13.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13) == 13) + + val f14 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int) => x14 + assert(f14.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14) == 14) + + val f15 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int) => x15 + assert(f15.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15) == 15) + + val f16 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int) => x16 + assert(f16.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16) == 16) + + val f17 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int) => x17 + assert(f17.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17) == 17) + + val f18 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int) => x18 + assert(f18.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17)(18) == 18) + + val f19 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int, x19: Int) => x19 + assert(f19.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17)(18)(19) == 19) + + val f20 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int, x19: Int, x20: Int) => x20 + assert(f20.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17)(18)(19)(20) == 20) + + val f21 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int, x19: Int, x20: Int, x21: Int) => x21 + assert(f21.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17)(18)(19)(20)(21) == 21) + + val f22 = (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int, x19: Int, x20: Int, x21: Int, x22: Int) => x22 + assert(f22.curried(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15)(16)(17)(18)(19)(20)(21)(22) == 22) +} \ No newline at end of file diff --git a/tests/init/neg/crash/i6109.scala.out b/tests/init/neg/crash/i6109.scala.out new file mode 100644 index 000000000000..e79fda4bfff8 --- /dev/null +++ b/tests/init/neg/crash/i6109.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/i6109.scala:1:7 ----------------------------------------- +1 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/i6109.scala:1 ] +one error found diff --git a/tests/init/neg/crash/i6858.scala b/tests/init/neg/crash/i6858.scala new file mode 100644 index 000000000000..d849e91733f1 --- /dev/null +++ b/tests/init/neg/crash/i6858.scala @@ -0,0 +1,10 @@ +object Test extends App { + inline def foo(ys: Int*): Unit = bar(ys: _*) + def bar(ys: Int*) = () + + val xs: Array[Int] = new Array[Int](3) + foo(xs: _*) + + val ys: Seq[Int] = new Array[Int](3) + foo(ys: _*) +} diff --git a/tests/init/neg/crash/i6858.scala.out b/tests/init/neg/crash/i6858.scala.out new file mode 100644 index 000000000000..b0743779c80d --- /dev/null +++ b/tests/init/neg/crash/i6858.scala.out @@ -0,0 +1,7 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/i6858.scala:1:7 ----------------------------------------- +1 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/i6858.scala:1 ] +one error found +there were 1 feature warning(s); re-run with -feature for details diff --git a/tests/init/neg/crash/i6914.scala b/tests/init/neg/crash/i6914.scala new file mode 100644 index 000000000000..ccdf0bd1a52d --- /dev/null +++ b/tests/init/neg/crash/i6914.scala @@ -0,0 +1,29 @@ +trait Expr[T] +trait Liftable[T] + +object test1 { + class ToExpr[T](given Liftable[T]) extends Conversion[T, Expr[T]] { + def apply(x: T): Expr[T] = ??? + } + given toExprFun[T](given Liftable[T]): ToExpr[T] + + given Liftable[Int] = ??? + given Liftable[String] = ??? + + def x = summon[ToExpr[String]] + def y = summon[Conversion[String, Expr[String]]] + + def a: Expr[String] = "abc" +} + +object test2 { + + given autoToExpr[T](given Liftable[T]) : Conversion[T, Expr[T]] { + def apply(x: T): Expr[T] = ??? + } + + given Liftable[Int] = ??? + given Liftable[String] = ??? + + def a: Expr[String] = "abc" +} \ No newline at end of file diff --git a/tests/init/neg/crash/opassign.scala b/tests/init/neg/crash/opassign.scala new file mode 100644 index 000000000000..8f6cad903a27 --- /dev/null +++ b/tests/init/neg/crash/opassign.scala @@ -0,0 +1,28 @@ +object opassign { + + var count: Int = 0 + + def next = { count += 1; count } + + var x: Int = 0 + x += 1 + + { var x: Int = 0 + x += 1 + } + + class Ref { + var x: Int = _ + } + val r = new Ref + r.x += 1 + + val arr = new Array[Int](10) + arr(0) += 1 + + def f(x: Int): Ref = new Ref + f(next).x += 1 + + val buf = new collection.mutable.ListBuffer[Int] + buf += 1 +} diff --git a/tests/init/neg/crash/private-leak.scala b/tests/init/neg/crash/private-leak.scala new file mode 100644 index 000000000000..f4afe02daf4c --- /dev/null +++ b/tests/init/neg/crash/private-leak.scala @@ -0,0 +1,9 @@ +package private_leak + +class Test { + private type Foo = Int + + val x: Foo = 1 + + x // error +} diff --git a/tests/init/neg/crash/rbtree.scala b/tests/init/neg/crash/rbtree.scala new file mode 100644 index 000000000000..6fa03938f5a3 --- /dev/null +++ b/tests/init/neg/crash/rbtree.scala @@ -0,0 +1,565 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2005-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala +package collection +package immutable + +import scala.annotation.tailrec +import scala.annotation.meta.getter + +/** An object containing the RedBlack tree implementation used by for `TreeMaps` and `TreeSets`. + * + * Implementation note: since efficiency is important for data structures this implementation + * uses `null` to represent empty trees. This also means pattern matching cannot + * easily be used. The API represented by the RedBlackTree object tries to hide these + * optimizations behind a reasonably clean API. + * + * @since 2.10 + */ +private[collection] +object RedBlackTree { + + def isEmpty(tree: Tree[_, _]): Boolean = tree eq null + + def contains[A: Ordering](tree: Tree[A, _], x: A): Boolean = lookup(tree, x) ne null + def get[A: Ordering, B](tree: Tree[A, B], x: A): Option[B] = lookup(tree, x) match { + case null => None + case tree => Some(tree.value) + } + + @tailrec + def lookup[A, B](tree: Tree[A, B], x: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + val cmp = ordering.compare(x, tree.key) + if (cmp < 0) lookup(tree.left, x) + else if (cmp > 0) lookup(tree.right, x) + else tree + } + + def count(tree: Tree[_, _]) = if (tree eq null) 0 else tree.count + /** + * Count all the nodes with keys greater than or equal to the lower bound and less than the upper bound. + * The two bounds are optional. + */ + def countInRange[A](tree: Tree[A, _], from: Option[A], to:Option[A])(implicit ordering: Ordering[A]) : Int = + if (tree eq null) 0 else + (from, to) match { + // with no bounds use this node's count + case (None, None) => tree.count + // if node is less than the lower bound, try the tree on the right, it might be in range + case (Some(lb), _) if ordering.lt(tree.key, lb) => countInRange(tree.right, from, to) + // if node is greater than or equal to the upper bound, try the tree on the left, it might be in range + case (_, Some(ub)) if ordering.gteq(tree.key, ub) => countInRange(tree.left, from, to) + // node is in range so the tree on the left will all be less than the upper bound and the tree on the + // right will all be greater than or equal to the lower bound. So 1 for this node plus + // count the subtrees by stripping off the bounds that we don't need any more + case _ => 1 + countInRange(tree.left, from, None) + countInRange(tree.right, None, to) + + } + def update[A: Ordering, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean): Tree[A, B1] = blacken(upd(tree, k, v, overwrite)) + def delete[A: Ordering, B](tree: Tree[A, B], k: A): Tree[A, B] = blacken(del(tree, k)) + def rangeImpl[A: Ordering, B](tree: Tree[A, B], from: Option[A], until: Option[A]): Tree[A, B] = (from, until) match { + case (Some(from), Some(until)) => this.range(tree, from, until) + case (Some(from), None) => this.from(tree, from) + case (None, Some(until)) => this.until(tree, until) + case (None, None) => tree + } + def range[A: Ordering, B](tree: Tree[A, B], from: A, until: A): Tree[A, B] = blacken(doRange(tree, from, until)) + def from[A: Ordering, B](tree: Tree[A, B], from: A): Tree[A, B] = blacken(doFrom(tree, from)) + def to[A: Ordering, B](tree: Tree[A, B], to: A): Tree[A, B] = blacken(doTo(tree, to)) + def until[A: Ordering, B](tree: Tree[A, B], key: A): Tree[A, B] = blacken(doUntil(tree, key)) + + def drop[A: Ordering, B](tree: Tree[A, B], n: Int): Tree[A, B] = blacken(doDrop(tree, n)) + def take[A: Ordering, B](tree: Tree[A, B], n: Int): Tree[A, B] = blacken(doTake(tree, n)) + def slice[A: Ordering, B](tree: Tree[A, B], from: Int, until: Int): Tree[A, B] = blacken(doSlice(tree, from, until)) + + def smallest[A, B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) throw new NoSuchElementException("empty map") + var result = tree + while (result.left ne null) result = result.left + result + } + def greatest[A, B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) throw new NoSuchElementException("empty map") + var result = tree + while (result.right ne null) result = result.right + result + } + + + def foreach[A,B,U](tree:Tree[A,B], f:((A,B)) => U):Unit = if (tree ne null) _foreach(tree,f) + + private[this] def _foreach[A, B, U](tree: Tree[A, B], f: ((A, B)) => U): Unit = { + if (tree.left ne null) _foreach(tree.left, f) + f((tree.key, tree.value)) + if (tree.right ne null) _foreach(tree.right, f) + } + + def foreachKey[A, U](tree:Tree[A,_], f: A => U):Unit = if (tree ne null) _foreachKey(tree,f) + + private[this] def _foreachKey[A, U](tree: Tree[A, _], f: A => U): Unit = { + if (tree.left ne null) _foreachKey(tree.left, f) + f((tree.key)) + if (tree.right ne null) _foreachKey(tree.right, f) + } + + def iterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None): Iterator[(A, B)] = new EntriesIterator(tree, start) + def keysIterator[A: Ordering](tree: Tree[A, _], start: Option[A] = None): Iterator[A] = new KeysIterator(tree, start) + def valuesIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None): Iterator[B] = new ValuesIterator(tree, start) + + @tailrec + def nth[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = { + val count = this.count(tree.left) + if (n < count) nth(tree.left, n) + else if (n > count) nth(tree.right, n - count - 1) + else tree + } + + def isBlack(tree: Tree[_, _]) = (tree eq null) || isBlackTree(tree) + + private[this] def isRedTree(tree: Tree[_, _]) = tree.isInstanceOf[RedTree[_, _]] + private[this] def isBlackTree(tree: Tree[_, _]) = tree.isInstanceOf[BlackTree[_, _]] + + private[this] def blacken[A, B](t: Tree[A, B]): Tree[A, B] = if (t eq null) null else t.black + + private[this] def mkTree[A, B](isBlack: Boolean, k: A, v: B, l: Tree[A, B], r: Tree[A, B]) = + if (isBlack) BlackTree(k, v, l, r) else RedTree(k, v, l, r) + + private[this] def balanceLeft[A, B, B1 >: B](isBlack: Boolean, z: A, zv: B, l: Tree[A, B1], d: Tree[A, B1]): Tree[A, B1] = { + if (isRedTree(l) && isRedTree(l.left)) + RedTree(l.key, l.value, BlackTree(l.left.key, l.left.value, l.left.left, l.left.right), BlackTree(z, zv, l.right, d)) + else if (isRedTree(l) && isRedTree(l.right)) + RedTree(l.right.key, l.right.value, BlackTree(l.key, l.value, l.left, l.right.left), BlackTree(z, zv, l.right.right, d)) + else + mkTree(isBlack, z, zv, l, d) + } + private[this] def balanceRight[A, B, B1 >: B](isBlack: Boolean, x: A, xv: B, a: Tree[A, B1], r: Tree[A, B1]): Tree[A, B1] = { + if (isRedTree(r) && isRedTree(r.left)) + RedTree(r.left.key, r.left.value, BlackTree(x, xv, a, r.left.left), BlackTree(r.key, r.value, r.left.right, r.right)) + else if (isRedTree(r) && isRedTree(r.right)) + RedTree(r.key, r.value, BlackTree(x, xv, a, r.left), BlackTree(r.right.key, r.right.value, r.right.left, r.right.right)) + else + mkTree(isBlack, x, xv, a, r) + } + private[this] def upd[A, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean)(implicit ordering: Ordering[A]): Tree[A, B1] = if (tree eq null) { + RedTree(k, v, null, null) + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) balanceLeft(isBlackTree(tree), tree.key, tree.value, upd(tree.left, k, v, overwrite), tree.right) + else if (cmp > 0) balanceRight(isBlackTree(tree), tree.key, tree.value, tree.left, upd(tree.right, k, v, overwrite)) + else if (overwrite || k != tree.key) mkTree(isBlackTree(tree), k, v, tree.left, tree.right) + else tree + } + private[this] def updNth[A, B, B1 >: B](tree: Tree[A, B], idx: Int, k: A, v: B1, overwrite: Boolean): Tree[A, B1] = if (tree eq null) { + RedTree(k, v, null, null) + } else { + val rank = count(tree.left) + 1 + if (idx < rank) balanceLeft(isBlackTree(tree), tree.key, tree.value, updNth(tree.left, idx, k, v, overwrite), tree.right) + else if (idx > rank) balanceRight(isBlackTree(tree), tree.key, tree.value, tree.left, updNth(tree.right, idx - rank, k, v, overwrite)) + else if (overwrite) mkTree(isBlackTree(tree), k, v, tree.left, tree.right) + else tree + } + + /* Based on Stefan Kahrs' Haskell version of Okasaki's Red&Black Trees + * http://www.cse.unsw.edu.au/~dons/data/RedBlackTree.html */ + private[this] def del[A, B](tree: Tree[A, B], k: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + def balance(x: A, xv: B, tl: Tree[A, B], tr: Tree[A, B]) = if (isRedTree(tl)) { + if (isRedTree(tr)) { + RedTree(x, xv, tl.black, tr.black) + } else if (isRedTree(tl.left)) { + RedTree(tl.key, tl.value, tl.left.black, BlackTree(x, xv, tl.right, tr)) + } else if (isRedTree(tl.right)) { + RedTree(tl.right.key, tl.right.value, BlackTree(tl.key, tl.value, tl.left, tl.right.left), BlackTree(x, xv, tl.right.right, tr)) + } else { + BlackTree(x, xv, tl, tr) + } + } else if (isRedTree(tr)) { + if (isRedTree(tr.right)) { + RedTree(tr.key, tr.value, BlackTree(x, xv, tl, tr.left), tr.right.black) + } else if (isRedTree(tr.left)) { + RedTree(tr.left.key, tr.left.value, BlackTree(x, xv, tl, tr.left.left), BlackTree(tr.key, tr.value, tr.left.right, tr.right)) + } else { + BlackTree(x, xv, tl, tr) + } + } else { + BlackTree(x, xv, tl, tr) + } + def subl(t: Tree[A, B]) = + if (t.isInstanceOf[BlackTree[_, _]]) t.red + else sys.error("Defect: invariance violation; expected black, got "+t) + + def balLeft(x: A, xv: B, tl: Tree[A, B], tr: Tree[A, B]) = if (isRedTree(tl)) { + RedTree(x, xv, tl.black, tr) + } else if (isBlackTree(tr)) { + balance(x, xv, tl, tr.red) + } else if (isRedTree(tr) && isBlackTree(tr.left)) { + RedTree(tr.left.key, tr.left.value, BlackTree(x, xv, tl, tr.left.left), balance(tr.key, tr.value, tr.left.right, subl(tr.right))) + } else { + sys.error("Defect: invariance violation") + } + def balRight(x: A, xv: B, tl: Tree[A, B], tr: Tree[A, B]) = if (isRedTree(tr)) { + RedTree(x, xv, tl, tr.black) + } else if (isBlackTree(tl)) { + balance(x, xv, tl.red, tr) + } else if (isRedTree(tl) && isBlackTree(tl.right)) { + RedTree(tl.right.key, tl.right.value, balance(tl.key, tl.value, subl(tl.left), tl.right.left), BlackTree(x, xv, tl.right.right, tr)) + } else { + sys.error("Defect: invariance violation") + } + def delLeft = if (isBlackTree(tree.left)) balLeft(tree.key, tree.value, del(tree.left, k), tree.right) else RedTree(tree.key, tree.value, del(tree.left, k), tree.right) + def delRight = if (isBlackTree(tree.right)) balRight(tree.key, tree.value, tree.left, del(tree.right, k)) else RedTree(tree.key, tree.value, tree.left, del(tree.right, k)) + def append(tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = if (tl eq null) { + tr + } else if (tr eq null) { + tl + } else if (isRedTree(tl) && isRedTree(tr)) { + val bc = append(tl.right, tr.left) + if (isRedTree(bc)) { + RedTree(bc.key, bc.value, RedTree(tl.key, tl.value, tl.left, bc.left), RedTree(tr.key, tr.value, bc.right, tr.right)) + } else { + RedTree(tl.key, tl.value, tl.left, RedTree(tr.key, tr.value, bc, tr.right)) + } + } else if (isBlackTree(tl) && isBlackTree(tr)) { + val bc = append(tl.right, tr.left) + if (isRedTree(bc)) { + RedTree(bc.key, bc.value, BlackTree(tl.key, tl.value, tl.left, bc.left), BlackTree(tr.key, tr.value, bc.right, tr.right)) + } else { + balLeft(tl.key, tl.value, tl.left, BlackTree(tr.key, tr.value, bc, tr.right)) + } + } else if (isRedTree(tr)) { + RedTree(tr.key, tr.value, append(tl, tr.left), tr.right) + } else if (isRedTree(tl)) { + RedTree(tl.key, tl.value, tl.left, append(tl.right, tr)) + } else { + sys.error("unmatched tree on append: " + tl + ", " + tr) + } + + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) delLeft + else if (cmp > 0) delRight + else append(tree.left, tree.right) + } + + private[this] def doFrom[A, B](tree: Tree[A, B], from: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(tree.key, from)) return doFrom(tree.right, from) + val newLeft = doFrom(tree.left, from) + if (newLeft eq tree.left) tree + else if (newLeft eq null) upd(tree.right, tree.key, tree.value, overwrite = false) + else rebalance(tree, newLeft, tree.right) + } + private[this] def doTo[A, B](tree: Tree[A, B], to: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(to, tree.key)) return doTo(tree.left, to) + val newRight = doTo(tree.right, to) + if (newRight eq tree.right) tree + else if (newRight eq null) upd(tree.left, tree.key, tree.value, overwrite = false) + else rebalance(tree, tree.left, newRight) + } + private[this] def doUntil[A, B](tree: Tree[A, B], until: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lteq(until, tree.key)) return doUntil(tree.left, until) + val newRight = doUntil(tree.right, until) + if (newRight eq tree.right) tree + else if (newRight eq null) upd(tree.left, tree.key, tree.value, overwrite = false) + else rebalance(tree, tree.left, newRight) + } + private[this] def doRange[A, B](tree: Tree[A, B], from: A, until: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(tree.key, from)) return doRange(tree.right, from, until) + if (ordering.lteq(until, tree.key)) return doRange(tree.left, from, until) + val newLeft = doFrom(tree.left, from) + val newRight = doUntil(tree.right, until) + if ((newLeft eq tree.left) && (newRight eq tree.right)) tree + else if (newLeft eq null) upd(newRight, tree.key, tree.value, overwrite = false) + else if (newRight eq null) upd(newLeft, tree.key, tree.value, overwrite = false) + else rebalance(tree, newLeft, newRight) + } + + private[this] def doDrop[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = { + if (n <= 0) return tree + if (n >= this.count(tree)) return null + val count = this.count(tree.left) + if (n > count) return doDrop(tree.right, n - count - 1) + val newLeft = doDrop(tree.left, n) + if (newLeft eq tree.left) tree + else if (newLeft eq null) updNth(tree.right, n - count - 1, tree.key, tree.value, overwrite = false) + else rebalance(tree, newLeft, tree.right) + } + private[this] def doTake[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = { + if (n <= 0) return null + if (n >= this.count(tree)) return tree + val count = this.count(tree.left) + if (n <= count) return doTake(tree.left, n) + val newRight = doTake(tree.right, n - count - 1) + if (newRight eq tree.right) tree + else if (newRight eq null) updNth(tree.left, n, tree.key, tree.value, overwrite = false) + else rebalance(tree, tree.left, newRight) + } + private[this] def doSlice[A, B](tree: Tree[A, B], from: Int, until: Int): Tree[A, B] = { + if (tree eq null) return null + val count = this.count(tree.left) + if (from > count) return doSlice(tree.right, from - count - 1, until - count - 1) + if (until <= count) return doSlice(tree.left, from, until) + val newLeft = doDrop(tree.left, from) + val newRight = doTake(tree.right, until - count - 1) + if ((newLeft eq tree.left) && (newRight eq tree.right)) tree + else if (newLeft eq null) updNth(newRight, from - count - 1, tree.key, tree.value, overwrite = false) + else if (newRight eq null) updNth(newLeft, until, tree.key, tree.value, overwrite = false) + else rebalance(tree, newLeft, newRight) + } + + // The zipper returned might have been traversed left-most (always the left child) + // or right-most (always the right child). Left trees are traversed right-most, + // and right trees are traversed leftmost. + + // Returns the zipper for the side with deepest black nodes depth, a flag + // indicating whether the trees were unbalanced at all, and a flag indicating + // whether the zipper was traversed left-most or right-most. + + // If the trees were balanced, returns an empty zipper + private[this] def compareDepth[A, B](left: Tree[A, B], right: Tree[A, B]): (NList[Tree[A, B]], Boolean, Boolean, Int) = { + import NList.cons + // Once a side is found to be deeper, unzip it to the bottom + def unzip(zipper: NList[Tree[A, B]], leftMost: Boolean): NList[Tree[A, B]] = { + val next = if (leftMost) zipper.head.left else zipper.head.right + if (next eq null) zipper + else unzip(cons(next, zipper), leftMost) + } + + // Unzip left tree on the rightmost side and right tree on the leftmost side until one is + // found to be deeper, or the bottom is reached + def unzipBoth(left: Tree[A, B], + right: Tree[A, B], + leftZipper: NList[Tree[A, B]], + rightZipper: NList[Tree[A, B]], + smallerDepth: Int): (NList[Tree[A, B]], Boolean, Boolean, Int) = { + if (isBlackTree(left) && isBlackTree(right)) { + unzipBoth(left.right, right.left, cons(left, leftZipper), cons(right, rightZipper), smallerDepth + 1) + } else if (isRedTree(left) && isRedTree(right)) { + unzipBoth(left.right, right.left, cons(left, leftZipper), cons(right, rightZipper), smallerDepth) + } else if (isRedTree(right)) { + unzipBoth(left, right.left, leftZipper, cons(right, rightZipper), smallerDepth) + } else if (isRedTree(left)) { + unzipBoth(left.right, right, cons(left, leftZipper), rightZipper, smallerDepth) + } else if ((left eq null) && (right eq null)) { + (null, true, false, smallerDepth) + } else if ((left eq null) && isBlackTree(right)) { + val leftMost = true + (unzip(cons(right, rightZipper), leftMost), false, leftMost, smallerDepth) + } else if (isBlackTree(left) && (right eq null)) { + val leftMost = false + (unzip(cons(left, leftZipper), leftMost), false, leftMost, smallerDepth) + } else { + sys.error("unmatched trees in unzip: " + left + ", " + right) + } + } + unzipBoth(left, right, null, null, 0) + } + + private[this] def rebalance[A, B](tree: Tree[A, B], newLeft: Tree[A, B], newRight: Tree[A, B]) = { + // This is like drop(n-1), but only counting black nodes + @tailrec + def findDepth(zipper: NList[Tree[A, B]], depth: Int): NList[Tree[A, B]] = + if (zipper eq null) { + sys.error("Defect: unexpected empty zipper while computing range") + } else if (isBlackTree(zipper.head)) { + if (depth == 1) zipper else findDepth(zipper.tail, depth - 1) + } else { + findDepth(zipper.tail, depth) + } + + // Blackening the smaller tree avoids balancing problems on union; + // this can't be done later, though, or it would change the result of compareDepth + val blkNewLeft = blacken(newLeft) + val blkNewRight = blacken(newRight) + val (zipper, levelled, leftMost, smallerDepth) = compareDepth(blkNewLeft, blkNewRight) + + if (levelled) { + BlackTree(tree.key, tree.value, blkNewLeft, blkNewRight) + } else { + val zipFrom = findDepth(zipper, smallerDepth) + val union = if (leftMost) { + RedTree(tree.key, tree.value, blkNewLeft, zipFrom.head) + } else { + RedTree(tree.key, tree.value, zipFrom.head, blkNewRight) + } + val zippedTree = NList.foldLeft(zipFrom.tail, union: Tree[A, B]) { (tree, node) => + if (leftMost) + balanceLeft(isBlackTree(node), node.key, node.value, tree, node.right) + else + balanceRight(isBlackTree(node), node.key, node.value, node.left, tree) + } + zippedTree + } + } + + // Null optimized list implementation for tree rebalancing. null presents Nil. + private[this] final class NList[A](val head: A, val tail: NList[A]) + + private[this] final object NList { // error + + def cons[B](x: B, xs: NList[B]): NList[B] = new NList(x, xs) + + def foldLeft[A, B](xs: NList[A], z: B)(f: (B, A) => B): B = { + var acc = z + var these = xs + while (these ne null) { + acc = f(acc, these.head) + these = these.tail + } + acc + } + + } + + /* + * Forcing direct fields access using the @inline annotation helps speed up + * various operations (especially smallest/greatest and update/delete). + * + * Unfortunately the direct field access is not guaranteed to work (but + * works on the current implementation of the Scala compiler). + * + * An alternative is to implement the these classes using plain old Java code... + */ + sealed abstract class Tree[A, +B]( + @(`inline` @getter) final val key: A, + @(`inline` @getter) final val value: B, + @(`inline` @getter) final val left: Tree[A, B], + @(`inline` @getter) final val right: Tree[A, B]) + extends Serializable { + @(`inline` @getter) final val count: Int = 1 + RedBlackTree.count(left) + RedBlackTree.count(right) + def black: Tree[A, B] + def red: Tree[A, B] + } + final class RedTree[A, +B](key: A, + value: B, + left: Tree[A, B], + right: Tree[A, B]) extends Tree[A, B](key, value, left, right) { + override def black: Tree[A, B] = BlackTree(key, value, left, right) + override def red: Tree[A, B] = this + override def toString: String = "RedTree(" + key + ", " + value + ", " + left + ", " + right + ")" + } + final class BlackTree[A, +B](key: A, + value: B, + left: Tree[A, B], + right: Tree[A, B]) extends Tree[A, B](key, value, left, right) { + override def black: Tree[A, B] = this + override def red: Tree[A, B] = RedTree(key, value, left, right) + override def toString: String = "BlackTree(" + key + ", " + value + ", " + left + ", " + right + ")" + } + + object RedTree { + inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) + def unapply[A, B](t: RedTree[A, B]) = Some((t.key, t.value, t.left, t.right)) + } + object BlackTree { + inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) + def unapply[A, B](t: BlackTree[A, B]) = Some((t.key, t.value, t.left, t.right)) + } + + private[this] abstract class TreeIterator[A, B, R](root: Tree[A, B], start: Option[A])(implicit ordering: Ordering[A]) extends Iterator[R] { + protected[this] def nextResult(tree: Tree[A, B]): R + + override def hasNext: Boolean = lookahead ne null + + override def next(): R = lookahead match { + case null => + throw new NoSuchElementException("next on empty iterator") + case tree => + lookahead = findLeftMostOrPopOnEmpty(goRight(tree)) + nextResult(tree) + } + +// @tailrec + private[this] def findLeftMostOrPopOnEmpty(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) popNext() + else if (tree.left eq null) tree + else findLeftMostOrPopOnEmpty(goLeft(tree)) + + private[this] def pushNext(tree: Tree[A, B]): Unit = { + try { + stackOfNexts(index) = tree + index += 1 + } catch { + case _: ArrayIndexOutOfBoundsException => + /* + * Either the tree became unbalanced or we calculated the maximum height incorrectly. + * To avoid crashing the iterator we expand the path array. Obviously this should never + * happen... + * + * An exception handler is used instead of an if-condition to optimize the normal path. + * This makes a large difference in iteration speed! + */ + assert(index >= stackOfNexts.length) + stackOfNexts :+= null + pushNext(tree) + } + } + private[this] def popNext(): Tree[A, B] = if (index == 0) null else { + index -= 1 + stackOfNexts(index) + } + + private[this] var stackOfNexts = if (root eq null) null else { + /* + * According to "Ralf Hinze. Constructing red-black trees" [http://www.cs.ox.ac.uk/ralf.hinze/publications/#P5] + * the maximum height of a red-black tree is 2*log_2(n + 2) - 2. + * + * According to {@see Integer#numberOfLeadingZeros} ceil(log_2(n)) = (32 - Integer.numberOfLeadingZeros(n - 1)) + * + * We also don't store the deepest nodes in the path so the maximum path length is further reduced by one. + */ + val maximumHeight = 2 * (32 - Integer.numberOfLeadingZeros(root.count + 2 - 1)) - 2 - 1 + new Array[Tree[A, B]](maximumHeight) + } + private[this] var index = 0 + private[this] var lookahead: Tree[A, B] = start map startFrom getOrElse findLeftMostOrPopOnEmpty(root) + + /** + * Find the leftmost subtree whose key is equal to the given key, or if no such thing, + * the leftmost subtree with the key that would be "next" after it according + * to the ordering. Along the way build up the iterator's path stack so that "next" + * functionality works. + */ + private[this] def startFrom(key: A) : Tree[A,B] = if (root eq null) null else { + @tailrec def find(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) popNext() + else find( + if (ordering.lteq(key, tree.key)) goLeft(tree) + else goRight(tree) + ) + find(root) + } + + private[this] def goLeft(tree: Tree[A, B]) = { + pushNext(tree) + tree.left + } + + private[this] def goRight(tree: Tree[A, B]) = tree.right + } + + private[this] class EntriesIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, (A, B)](tree, focus) { + override def nextResult(tree: Tree[A, B]) = (tree.key, tree.value) + } + + private[this] class KeysIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, A](tree, focus) { + override def nextResult(tree: Tree[A, B]) = tree.key + } + + private[this] class ValuesIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, B](tree, focus) { + override def nextResult(tree: Tree[A, B]) = tree.value + } +} + +object Test { + def main(args: Array[String]) = {} +} diff --git a/tests/init/neg/crash/t0055.scala b/tests/init/neg/crash/t0055.scala new file mode 100644 index 000000000000..ca503dfed81e --- /dev/null +++ b/tests/init/neg/crash/t0055.scala @@ -0,0 +1,6 @@ +class X(_x : Any) +class W { + new X(new Z() with Y) {} + trait Y { def y = () } +} +class Z(r : Any) { def this() = this(null) } diff --git a/tests/init/neg/crash/t10032.scala b/tests/init/neg/crash/t10032.scala new file mode 100644 index 000000000000..f7e8ef459f3a --- /dev/null +++ b/tests/init/neg/crash/t10032.scala @@ -0,0 +1,164 @@ +object Test extends App { + def a1(): Unit = println(" a1") + def a2(): Unit = println(" a2") + def a3(): Unit = println(" a3") + + def i1: Int = { println(" i1"); 1 } + def i2: Int = { println(" i2"); 2 } + def i3: Int = { println(" i3"); 3 } + + def e1: Int = { println(" e1"); throw new Exception() } + + def t1: Int = { + println("t1") + try { + synchronized { return i1 } + } finally { + synchronized { a1() } + } + } + + def t2: Int = { + println("t2") + try { + try { return i1 } + finally { a1() } + } finally { + try { a2() } + finally { a3() } + } + } + + def t3(i: => Int): Int = { + println("t3") + try { + try { return i } + finally { a1() } + } catch { + case _: Throwable => + try { i2 } + finally { a2() } // no cleanup version + } finally { + a3() + } + } + + def t4(i: => Int): Int = { + println("t4") + try { + return i + } finally { + return i2 + } + } + + def t5(i: => Int): Int = { + println("t5") + try { + try { + try { return i } + finally { a1() } + } catch { + case _: Throwable => i2 + } + } finally { + a3() + } + } + + def t6(i: => Int): Int = { + println("t6") + try { + try { return i } + finally { return i2 } + } finally { + return i3 + } + } + + def t7(i: => Int): Int = { + println("t7") + try { i } + catch { + case _: Throwable => + return i2 + } finally { + a1() // cleanup required, early return in handler + } + } + + def t8(i: => Int): Int = { + println("t8") + try { + try { i } + finally { // no cleanup version + try { return i2 } + finally { a1() } // cleanup version required + } + } finally { // cleanup version required + a2() + } + } + + def t9(i: => Int): Int = { + println("t9") + try { + return i + } finally { + try { return i2 } + finally { a1() } + } + } + + def t10(i: => Int): Int = { + println("t10") + try { + return i + } finally { + try { return i2 } + finally { return i3 } + } + } + + // this changed semantics between 2.12.0 and 2.12.1, see https://github.com/scala/scala/pull/5509#issuecomment-259291609 + def t11(i: => Int): Int = { + println("t11") + try { + try { return i } + finally { return i2 } + } finally { + a1() + } + } + + assert(t1 == 1) + + assert(t2 == 1) + + assert(t3(i1) == 1) + assert(t3(e1) == 2) + + assert(t4(i1) == 2) + assert(t4(e1) == 2) + + assert(t5(i1) == 1) + assert(t5(e1) == 2) + + assert(t6(i1) == 3) + assert(t6(e1) == 3) + + assert(t7(i1) == 1) + assert(t7(e1) == 2) + + assert(t8(i1) == 2) + assert(t8(e1) == 2) + + assert(t9(i1) == 2) + assert(t9(e1) == 2) + + assert(t10(i1) == 3) + assert(t10(e1) == 3) + + assert(t11(i1) == 2) + assert(t11(e1) == 2) +} diff --git a/tests/init/neg/crash/t10032.scala.out b/tests/init/neg/crash/t10032.scala.out new file mode 100644 index 000000000000..e75524a628d2 --- /dev/null +++ b/tests/init/neg/crash/t10032.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/t10032.scala:1:7 ---------------------------------------- +1 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/t10032.scala:1 ] +one error found diff --git a/tests/init/neg/crash/t2111.scala b/tests/init/neg/crash/t2111.scala new file mode 100644 index 000000000000..98177c4e216f --- /dev/null +++ b/tests/init/neg/crash/t2111.scala @@ -0,0 +1,20 @@ + +object Test extends App { + + object Color extends Enumeration { + val Red, Green, Blue = Value + } + + class MyColor extends Enumeration { + val Red, Green, Blue = Value + } + + println(Color.Red) + println(Color.Green) + println(Color.Blue) + val col = new MyColor + println(col.Blue) + println(col.Green) + println(col.Red) + +} diff --git a/tests/init/neg/crash/t2111.scala.out b/tests/init/neg/crash/t2111.scala.out new file mode 100644 index 000000000000..d4ebc338d1c9 --- /dev/null +++ b/tests/init/neg/crash/t2111.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/t2111.scala:2:7 ----------------------------------------- +2 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/t2111.scala:2 ] +one error found diff --git a/tests/init/neg/crash/t7120/Base_1.scala b/tests/init/neg/crash/t7120/Base_1.scala new file mode 100644 index 000000000000..be07b4f34f93 --- /dev/null +++ b/tests/init/neg/crash/t7120/Base_1.scala @@ -0,0 +1,10 @@ +// This bug doesn't depend on separate compilation, +// in the interests of minimizing the log output during +// debugging this problem, I've split the compilation. + +case class Container( v: String ) + +trait Base[ T <: AnyRef ] { + type UserType = T + protected def defect: PartialFunction[ UserType, String ] +} diff --git a/tests/init/neg/crash/t7120/Derived_2.scala b/tests/init/neg/crash/t7120/Derived_2.scala new file mode 100644 index 000000000000..e0de629f82de --- /dev/null +++ b/tests/init/neg/crash/t7120/Derived_2.scala @@ -0,0 +1,9 @@ +trait Derived extends Base[ Container ] { + protected def defect = { case c: Container => c.v.toString } +} + +// Erasure was ignoring the prefix `Derived#7001.this` when erasing +// A1, and consequently used `Object` rather than `Container`, which +// was only seen because that signature clashed with the bridge method. +// +// applyOrElse[A1 <: Derived#7001.this.UserType#7318, B1 >: String](x1: A1) diff --git a/tests/init/neg/crash/t7120/Run_3.scala b/tests/init/neg/crash/t7120/Run_3.scala new file mode 100644 index 000000000000..95e7f994fffc --- /dev/null +++ b/tests/init/neg/crash/t7120/Run_3.scala @@ -0,0 +1,3 @@ +object Test extends Derived with App { + println( defect( Container( "8" ) ) ) +} diff --git a/tests/init/neg/crash/t8002.scala b/tests/init/neg/crash/t8002.scala new file mode 100644 index 000000000000..f24a213dea52 --- /dev/null +++ b/tests/init/neg/crash/t8002.scala @@ -0,0 +1,19 @@ +object Test extends App { + val a: Any = { + class A private () { private def x = 0; A.y }; + object A { + def a = new A().x + private def y = 0 + } + A.a + } + def b: Any = { + object A { + def a = new A().x + private def y = 0 + } + class A private () { private def x = 0; A.y }; + A.a + } + b +} diff --git a/tests/init/neg/crash/t8002.scala.out b/tests/init/neg/crash/t8002.scala.out new file mode 100644 index 000000000000..f9c4d4234c1d --- /dev/null +++ b/tests/init/neg/crash/t8002.scala.out @@ -0,0 +1,6 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/t8002.scala:1:7 ----------------------------------------- +1 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/t8002.scala:1 ] +one error found diff --git a/tests/init/neg/crash/tuples.scala b/tests/init/neg/crash/tuples.scala new file mode 100644 index 000000000000..2ab97cb4aec3 --- /dev/null +++ b/tests/init/neg/crash/tuples.scala @@ -0,0 +1,31 @@ +import Function._ + +object Test extends App { + var xyz: (Int, String, Boolean) = _ // error + xyz = (1, "abc", true) + Console.println(xyz) + xyz match { + case (1, "abc", true) => Console.println("OK") + case _ => ??? + } + def func(x: Int, y: String, z: Double): Unit = { + Console.println("x = " + x + "; y = " + y + "; z = " + z); + } + + def params = (2, "xxx", 3.14159) // (*****) + + tupled(func _)(params) // call the function with all the params at once + func(2, "xxx", 3.14159) // the same call + (func _).apply(2, "xxx", 3.14159) // the same call + + // Composing a tuple + def t = (1, "Hello", false) + + // Decomposing a tuple + val (i, s, b) = t + + // all the assertions are passed + assert(i == 1) + assert(s == "Hello") + assert(b == false) +} diff --git a/tests/init/neg/crash/tuples.scala.out b/tests/init/neg/crash/tuples.scala.out new file mode 100644 index 000000000000..bdd859421fd0 --- /dev/null +++ b/tests/init/neg/crash/tuples.scala.out @@ -0,0 +1,12 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/tuples.scala:3:7 ---------------------------------------- +3 |object Test extends App { + | ^ + | Leaking of this. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/tuples.scala:3 ] +-- Error: /Users/fliu/Documents/scala-init-checker/tests/crash/tuples.scala:4:36 --------------------------------------- +4 | var xyz: (Int, String, Boolean) = _ // error + | ^ + | Access non-initialized field Test.xyz. Calling trace: + | -> object Test extends App { [ /Users/fliu/Documents/scala-init-checker/tests/crash/tuples.scala:3 ] + | -> Console.println(xyz) [ /Users/fliu/Documents/scala-init-checker/tests/crash/tuples.scala:6 ] +two errors found diff --git a/tests/init/neg/crash/virtpatmat_alts.scala b/tests/init/neg/crash/virtpatmat_alts.scala new file mode 100644 index 000000000000..5823e06edc68 --- /dev/null +++ b/tests/init/neg/crash/virtpatmat_alts.scala @@ -0,0 +1,15 @@ +/* + * filter: It would fail on the following input + */ +object Test { + (true, true) match { // error + case (true, true) | (false, false) => 1 + } + + List(5) match { // error + case 1 :: Nil | 2 :: Nil => println("FAILED") + case (x@(4 | 5 | 6)) :: Nil => println("OK "+ x) + case 7 :: Nil => println("FAILED") + case Nil => println("FAILED") + } +} diff --git a/tests/init/neg/features/billion-dollar-fix.scala b/tests/init/neg/features/billion-dollar-fix.scala new file mode 100644 index 000000000000..42a9526f6008 --- /dev/null +++ b/tests/init/neg/features/billion-dollar-fix.scala @@ -0,0 +1,34 @@ +import scala.annotation._ + +object Bilion { + class Security(_dba: => DBA) { + def work(): Unit = ??? + } + class DBA(_s: => Security, _gui: => GUI) { + def work(): Unit = ??? + } + class SMS(_s: => Security, _dba: => DBA) { + def work(): Unit = ??? + } + class GUI(_s: => Security, _sms: => SMS, _dba: => DBA) { + def work(): Unit = ??? + } +} + +import Bilion._ + +final class Billion1 { + val s: Security = new Security(dba) + val dba: DBA = new DBA(s, gui) + + s.work() + dba.work() + + val sms: SMS = new SMS(s, dba) + val gui: GUI = new GUI(s, sms, dba) + + s.work() // ok + dba.work() // ok + sms.work() // ok + gui.work() // ok +} diff --git a/tests/init/neg/features/doublelist.scala b/tests/init/neg/features/doublelist.scala new file mode 100644 index 000000000000..77b4999bf4a6 --- /dev/null +++ b/tests/init/neg/features/doublelist.scala @@ -0,0 +1,6 @@ +class DoubleList { + class Node(var prev: Node, var next: Node, data: Int) + object sentinel extends Node(sentinel, sentinel, 0) + + def insert(x: Int) = ??? +} diff --git a/tests/init/neg/features/even.scala b/tests/init/neg/features/even.scala new file mode 100644 index 000000000000..d8882b96f66d --- /dev/null +++ b/tests/init/neg/features/even.scala @@ -0,0 +1,5 @@ +class Foo { + val even: Int => Boolean = (n: Int) => n == 0 || odd(n - 1) + val odd: Int => Boolean = (n: Int) => n == 1 || even(n - 1) + val flag: Boolean = odd(6) +} diff --git a/tests/init/neg/features/high-order.scala b/tests/init/neg/features/high-order.scala new file mode 100644 index 000000000000..60c15545cc14 --- /dev/null +++ b/tests/init/neg/features/high-order.scala @@ -0,0 +1,9 @@ +abstract class Parent { + val f: () => String = () => this.message + def message: String +} +class Child extends Parent { + val a = f() + val b = "hello" // error + def message: String = b +} diff --git a/tests/init/neg/features/linearization.scala b/tests/init/neg/features/linearization.scala new file mode 100644 index 000000000000..15c3c0aca2d4 --- /dev/null +++ b/tests/init/neg/features/linearization.scala @@ -0,0 +1,10 @@ +trait TA { + val x = "world" +} + +trait TB { + def x: String + val m = "hello" + x +} +class Foo extends TA with TB // OK +class Bar extends TB with TA // error diff --git a/tests/init/neg/features/scalacheck.scala b/tests/init/neg/features/scalacheck.scala new file mode 100644 index 000000000000..ad7f8eabfc70 --- /dev/null +++ b/tests/init/neg/features/scalacheck.scala @@ -0,0 +1,16 @@ +class Properties(val name: String) { + + private val props = new scala.collection.mutable.ListBuffer[(String,Any)] + + sealed class PropertySpecifier() { + def update(propName: String, p: => Boolean) = { + props += ((name+"."+propName, () => p)) + } + } + + val property = new PropertySpecifier() +} + +object PropSpecification extends Properties("Prop") { + property("Prop.==> undecided") = true +} diff --git a/tests/init/neg/features/trees.scala b/tests/init/neg/features/trees.scala new file mode 100644 index 000000000000..867e6636bb12 --- /dev/null +++ b/tests/init/neg/features/trees.scala @@ -0,0 +1,6 @@ +class Trees { + class ValDef { counter += 1 } + class EmptyValDef extends ValDef + val theEmptyValDef = new EmptyValDef + private var counter = 0 // error +} diff --git a/tests/init/neg/freedom/field-usage.scala b/tests/init/neg/freedom/field-usage.scala new file mode 100644 index 000000000000..97a5b423d2a9 --- /dev/null +++ b/tests/init/neg/freedom/field-usage.scala @@ -0,0 +1,14 @@ +class A { + private[this] val a: Int = 10 + private[this] val b: Int = a * a +} + +class B { + private[this] val b: Int = a * 5 // error + private[this] val a: Int = 10 +} + +class C { + val a: Int = 10 + val b: Int = a * 5 // error: public field is method call +} \ No newline at end of file diff --git a/tests/init/neg/freedom/method-usage.scala b/tests/init/neg/freedom/method-usage.scala new file mode 100644 index 000000000000..a762cd624f1a --- /dev/null +++ b/tests/init/neg/freedom/method-usage.scala @@ -0,0 +1,4 @@ +class A { + val a: Int = f() // error + def f(): Int = a * a +} \ No newline at end of file diff --git a/tests/init/neg/freedom/outer.scala.out b/tests/init/neg/freedom/outer.scala.out new file mode 100644 index 000000000000..1889bfedf8b7 --- /dev/null +++ b/tests/init/neg/freedom/outer.scala.out @@ -0,0 +1,5 @@ +-- Error: /Users/fliu/Documents/scala-init-checker/tests/freedom/outer.scala:3:17 -------------------------------------- +3 | val b: Int = a * 10 // always treat outer as initialized, thus allow `A.this.a` + | ^ + | [initialization] calling method A$B$$$outer on this +one error found diff --git a/tests/init/neg/freedom/tolerant-boundary/child_2.scala b/tests/init/neg/freedom/tolerant-boundary/child_2.scala new file mode 100644 index 000000000000..be9ddfabfaac --- /dev/null +++ b/tests/init/neg/freedom/tolerant-boundary/child_2.scala @@ -0,0 +1,3 @@ +class Child extends Base { + val x = a + foo // don't report error cross boundary +} \ No newline at end of file diff --git a/tests/init/neg/freedom/tolerant-boundary/parent_1.scala b/tests/init/neg/freedom/tolerant-boundary/parent_1.scala new file mode 100644 index 000000000000..d5f225bf1b61 --- /dev/null +++ b/tests/init/neg/freedom/tolerant-boundary/parent_1.scala @@ -0,0 +1,4 @@ +class Base { + val a: Int = 10 + def foo: Int = a * a +} \ No newline at end of file diff --git a/tests/init/neg/functions/even.scala b/tests/init/neg/functions/even.scala new file mode 100644 index 000000000000..d8882b96f66d --- /dev/null +++ b/tests/init/neg/functions/even.scala @@ -0,0 +1,5 @@ +class Foo { + val even: Int => Boolean = (n: Int) => n == 0 || odd(n - 1) + val odd: Int => Boolean = (n: Int) => n == 1 || even(n - 1) + val flag: Boolean = odd(6) +} diff --git a/tests/init/neg/functions/function1.scala b/tests/init/neg/functions/function1.scala new file mode 100644 index 000000000000..e01864ae1f47 --- /dev/null +++ b/tests/init/neg/functions/function1.scala @@ -0,0 +1,12 @@ +class Foo { + val x = "hello" + val fun1: Int => Int = n => 0 + n + list.size + val fun2: Int => Int = n => 1 + n + list.size + fun2(5) + + List(5, 9).map(n => 2 + n + list.size) + + final val list = List(1, 2, 3) // error + + List(5, 9).map(n => 3 + n + list.size) +} \ No newline at end of file diff --git a/tests/init/neg/functions/function10.scala b/tests/init/neg/functions/function10.scala new file mode 100644 index 000000000000..334ff866cea7 --- /dev/null +++ b/tests/init/neg/functions/function10.scala @@ -0,0 +1,7 @@ +class Base { self => + (0 to 10).foreach { i => + println(a) + } + + val a = 10 // error +} \ No newline at end of file diff --git a/tests/init/neg/functions/function11.scala b/tests/init/neg/functions/function11.scala new file mode 100644 index 000000000000..2612303c796f --- /dev/null +++ b/tests/init/neg/functions/function11.scala @@ -0,0 +1,33 @@ +final class Capture { + private[this] var m: Boolean = false + + (0 to 10).foreach { i => + f() + } + + val a = 10 // error + + def f() = while ({ + println(a) + m + }) () +} + +final class Capture2 { + private[this] var m: Boolean = false + + (0 to 10).foreach { i => + f() + } + + val a = 10 + + def f() = while ({ + m = false + m + }) () + + (0 to 10).foreach { i => + f() + } +} \ No newline at end of file diff --git a/tests/init/neg/functions/function2.scala b/tests/init/neg/functions/function2.scala new file mode 100644 index 000000000000..0a0f44e2744e --- /dev/null +++ b/tests/init/neg/functions/function2.scala @@ -0,0 +1,6 @@ +final class Foo { + def fun: Int => Int = n => n + x.size + fun(5) + + val x = "hello" // error +} \ No newline at end of file diff --git a/tests/init/neg/functions/high-order.scala b/tests/init/neg/functions/high-order.scala new file mode 100644 index 000000000000..60c15545cc14 --- /dev/null +++ b/tests/init/neg/functions/high-order.scala @@ -0,0 +1,9 @@ +abstract class Parent { + val f: () => String = () => this.message + def message: String +} +class Child extends Parent { + val a = f() + val b = "hello" // error + def message: String = b +} diff --git a/tests/init/neg/hybrid/hybrid1.scala b/tests/init/neg/hybrid/hybrid1.scala new file mode 100644 index 000000000000..6fa6c9ec215a --- /dev/null +++ b/tests/init/neg/hybrid/hybrid1.scala @@ -0,0 +1,21 @@ +trait A { + def g: Int +} + +class X(_y: Y) { + class B extends A { + def g = _y.n + } +} + +class Y { + val x = new X(this) + + class C extends x.B { + g + } + + new C + + val n = 10 // error +} diff --git a/tests/init/neg/hybrid/hybrid2.scala b/tests/init/neg/hybrid/hybrid2.scala new file mode 100644 index 000000000000..8bf85d4867c3 --- /dev/null +++ b/tests/init/neg/hybrid/hybrid2.scala @@ -0,0 +1,18 @@ +trait A { + def g: Int +} + +class X(_y: Y) { + class B extends A { + def g = _y.n // error + } + + val b = new B +} + +class Y { + val x = new X(this) + x.b.g + + val n = 10 +} diff --git a/tests/init/neg/hybrid/hybrid4.scala b/tests/init/neg/hybrid/hybrid4.scala new file mode 100644 index 000000000000..008f9613a14f --- /dev/null +++ b/tests/init/neg/hybrid/hybrid4.scala @@ -0,0 +1,12 @@ +class Foo { + class Inner { + val len = list.size + } + + val bar: Bar = new Bar(this) + val list = List(1, 2, 3) // error +} + +class Bar(val _foo: Foo) { + val inner = new _foo.Inner +} \ No newline at end of file diff --git a/tests/init/neg/hybrid/hybrid5.scala b/tests/init/neg/hybrid/hybrid5.scala new file mode 100644 index 000000000000..910104157253 --- /dev/null +++ b/tests/init/neg/hybrid/hybrid5.scala @@ -0,0 +1,19 @@ +class Foo { + val bar = new Bar(this) + var x: bar.Inner = new bar.Inner + + class Inner { + val len = list.size // error + } + + val list = List(1, 2, 3) +} + +class Bar(val _foo: Foo) { + class Inner { + val x = g + val len = x.len + } + + def g = new _foo.Inner +} \ No newline at end of file diff --git a/tests/init/neg/hybrid/hybrid6.scala b/tests/init/neg/hybrid/hybrid6.scala new file mode 100644 index 000000000000..eced1b9d4554 --- /dev/null +++ b/tests/init/neg/hybrid/hybrid6.scala @@ -0,0 +1,15 @@ +class Foo { + val bar = new Bar(this) + var x: bar.Inner = new bar.Inner + + val list = List(1, 2, 3) // error +} + +class Bar(val _foo: Foo) { + class Inner { + val x = g + val len = x.size + } + + def g = _foo.list +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner-case.scala b/tests/init/neg/inner-outer/inner-case.scala new file mode 100644 index 000000000000..fa4ea0250884 --- /dev/null +++ b/tests/init/neg/inner-outer/inner-case.scala @@ -0,0 +1,11 @@ +class Foo { + case class Inner(x: Int) { + def f() = count + x + } + + val a = Inner(5) // ok + println(a) // error + + var count = 0 + println(a) // ok +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner-loop.scala b/tests/init/neg/inner-outer/inner-loop.scala new file mode 100644 index 000000000000..a7ff5c153d32 --- /dev/null +++ b/tests/init/neg/inner-outer/inner-loop.scala @@ -0,0 +1,15 @@ +class Outer { outer => + class Inner extends Outer { + val x = 5 + outer.n + } + val inner = new Inner + val n = 6 // error +} + +class Outer2 { outer => + class Inner extends Outer2 { + val x = 5 + n + } + val inner = new Inner + val n = 6 +} diff --git a/tests/init/neg/inner-outer/inner-new.scala b/tests/init/neg/inner-outer/inner-new.scala new file mode 100644 index 000000000000..016179a1d7fc --- /dev/null +++ b/tests/init/neg/inner-outer/inner-new.scala @@ -0,0 +1,11 @@ +class Foo { + class Inner { + def f() = count + 1 + } + + val a = new Inner // ok + println(a) // error + + var count = 0 + println(a) // ok +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner1.scala b/tests/init/neg/inner-outer/inner1.scala new file mode 100644 index 000000000000..6e8077a500b0 --- /dev/null +++ b/tests/init/neg/inner-outer/inner1.scala @@ -0,0 +1,18 @@ +class Foo { + new this.Inner + + val list = List(1, 2, 3) // error, as Inner access `this.list` + + val inner: Inner = new this.Inner // ok, `list` is instantiated + lib.escape(inner) // error + + val name = "good" + + class Inner { + val len = list.size + } +} + +object lib { + def escape(x: Foo#Inner): Unit = ??? +} diff --git a/tests/init/neg/inner-outer/inner11.scala b/tests/init/neg/inner-outer/inner11.scala new file mode 100644 index 000000000000..7a96a29f8a25 --- /dev/null +++ b/tests/init/neg/inner-outer/inner11.scala @@ -0,0 +1,35 @@ +object NameKinds { + abstract class NameInfo + + abstract class NameKind(val tag: Int) { self => + type ThisInfo <: Info + + class Info extends NameInfo { this: ThisInfo => + def kind = self + } + } + + class ClassifiedNameKind(tag: Int, val infoString: String) extends NameKind(tag) { + type ThisInfo = Info + val info: Info = new Info + println(info.kind) // error + } +} + +object NameKinds2 { + abstract class NameInfo + + abstract class NameKind(val tag: Int) { self => + type ThisInfo <: Info + + class Info extends NameInfo { this: ThisInfo => + def kind = "info" + } + } + + class ClassifiedNameKind(tag: Int, val infoString: String) extends NameKind(tag) { + type ThisInfo = Info + val info: Info = new Info + println(info.kind) // ok + } +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner13.scala b/tests/init/neg/inner-outer/inner13.scala new file mode 100644 index 000000000000..356083a7cf1a --- /dev/null +++ b/tests/init/neg/inner-outer/inner13.scala @@ -0,0 +1,10 @@ +final class A { + val x = 10 + + final class Inner { + def f(n: Int) = println(this) + val y = (n: Int) => f(20) + } + + new Inner // check for recursion +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner15.scala b/tests/init/neg/inner-outer/inner15.scala new file mode 100644 index 000000000000..59d1daac404f --- /dev/null +++ b/tests/init/neg/inner-outer/inner15.scala @@ -0,0 +1,19 @@ +class A { + val x = "hello" + + class Inner1 { + def f(n: Int) = println(new Inner1) + val y = (n: Int) => f(20) + } + + class Inner2 { + val y = x + } +} + +class B extends A { + new Inner1 + new Inner2 + + override val x = "world" // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner16.scala b/tests/init/neg/inner-outer/inner16.scala new file mode 100644 index 000000000000..61271396a074 --- /dev/null +++ b/tests/init/neg/inner-outer/inner16.scala @@ -0,0 +1,17 @@ +class A { + object O { + class B { + val a = y + } + class C + } + + class Inner { + def f(n: String) = new O.C + } + + val inner = new Inner + val b = new O.B + + val y = 10 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner17.scala b/tests/init/neg/inner-outer/inner17.scala new file mode 100644 index 000000000000..feb1c2b10229 --- /dev/null +++ b/tests/init/neg/inner-outer/inner17.scala @@ -0,0 +1,13 @@ +class A { + val f: Int = 10 + + class B { + val a = f + } + + println(new B) // error +} + +class C extends A { + override val f: Int = 20 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner19.scala b/tests/init/neg/inner-outer/inner19.scala new file mode 100644 index 000000000000..f9c0a29a1649 --- /dev/null +++ b/tests/init/neg/inner-outer/inner19.scala @@ -0,0 +1,19 @@ +class A { + object O { + val x = 10 + class B { + val y = n + def f: Int = n + } + + case class C(a: Int) + } + + val n = 20 +} + +class B extends A { + println((new O.B).f) + O.C(4) + override val n = 50 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner20.scala b/tests/init/neg/inner-outer/inner20.scala new file mode 100644 index 000000000000..1553e09eeeb0 --- /dev/null +++ b/tests/init/neg/inner-outer/inner20.scala @@ -0,0 +1,18 @@ +class A { + class O { + val x = 10 + + class B { + val y = n + def f: Int = n + } + } + + val n = 20 +} + +class B extends A { + val o = new O + println((new o.B).f) + override val n = 50 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner21.scala b/tests/init/neg/inner-outer/inner21.scala new file mode 100644 index 000000000000..c2277163acc3 --- /dev/null +++ b/tests/init/neg/inner-outer/inner21.scala @@ -0,0 +1,26 @@ +class X { + object A { + name.size + def foo: Int = name.size + def bar: Int = 10 + } + + A.foo + A.bar + + val name = "jack" // error +} + + +class Y { + class A { + name.size + def foo: Int = name.size + def bar: Int = 10 + } + + (new A).foo + (new A).bar + + val name = "jack" // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner22.scala b/tests/init/neg/inner-outer/inner22.scala new file mode 100644 index 000000000000..aa5a381f2d49 --- /dev/null +++ b/tests/init/neg/inner-outer/inner22.scala @@ -0,0 +1,32 @@ +abstract class A { + def f: () => Int + val m = f + + def g: Int +} + +class C { + class B extends A { + def f = () => x + def g = 10 + } + + new B + + val x = 10 +} + +class D { + class B extends A { + def f = () => 5 + def g = x + } + + class C extends B { + g + } + + new C + + val x = 10 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner23.scala b/tests/init/neg/inner-outer/inner23.scala new file mode 100644 index 000000000000..5d373c649ce2 --- /dev/null +++ b/tests/init/neg/inner-outer/inner23.scala @@ -0,0 +1,13 @@ + +class Trees { + class ValDef { + def setMods(x: Int) = name.size + } + + class EmptyValDef extends ValDef { + setMods(5) + } + + val theEmptyValDef = new EmptyValDef + val name = "hello" // error +} diff --git a/tests/init/neg/inner-outer/inner24.scala b/tests/init/neg/inner-outer/inner24.scala new file mode 100644 index 000000000000..f8a60c1bb311 --- /dev/null +++ b/tests/init/neg/inner-outer/inner24.scala @@ -0,0 +1,18 @@ +trait Foo { + class A + + class B { + foo(10) + } + + def foo(x: Int) = 5 + x +} + + +class Bar extends Foo { + val a: A = new A // OK + val b = new B + + override def foo(x: Int) = x + id + val id = 100 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner25.scala b/tests/init/neg/inner-outer/inner25.scala new file mode 100644 index 000000000000..8cf99539b849 --- /dev/null +++ b/tests/init/neg/inner-outer/inner25.scala @@ -0,0 +1,14 @@ +class A[K, V] { self => + def foreach[U](f: (K, V) => U): Unit = println(a) + def withFilter(p: (K, V) => Boolean): A[K, V] = ??? + + class O { + def foreach[U](f: V => U): Unit = self.foreach { + case (k, v) => f(v) // `k` has type `K` instead of a TermRef + } + } + + println(new O) // error + + val a = 10 +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner28.scala b/tests/init/neg/inner-outer/inner28.scala new file mode 100644 index 000000000000..0ced985925d8 --- /dev/null +++ b/tests/init/neg/inner-outer/inner28.scala @@ -0,0 +1,21 @@ +object Test { + def f1c(x: Int) = { + class T1 { + def f2 = { + trait T2 { + def f3: Int = { + def f4 = 10 + def f5 = f4 + def f7 = this.f3 + f5 + } + def f3a = f3 + } + class C2 extends T2 + class C3 extends T1 + new C2().f3a + new C3().f6 + } + def f6 = 10 + } + } +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner29.scala b/tests/init/neg/inner-outer/inner29.scala new file mode 100644 index 000000000000..740352db1e4e --- /dev/null +++ b/tests/init/neg/inner-outer/inner29.scala @@ -0,0 +1,21 @@ +class A(x: Int) { + def f: Int = 10 + class Inner { + def g: Int = f + } +} + +abstract class B(n: Int) { + val a: A + val inner = new a.Inner +} + +class C extends B(5) { + class E extends A(10) { + override def f: Int = x + } + + val a = new E // error: init too late + + val x = 10 +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner4.scala b/tests/init/neg/inner-outer/inner4.scala new file mode 100644 index 000000000000..5fc4d2b9e053 --- /dev/null +++ b/tests/init/neg/inner-outer/inner4.scala @@ -0,0 +1,10 @@ +class Foo(val foo1: Foo) { + class Inner { + val len = name.size + } + + new this.Inner + new foo1.Inner + + val name = "hello" // error +} diff --git a/tests/init/neg/inner-outer/inner5.scala b/tests/init/neg/inner-outer/inner5.scala new file mode 100644 index 000000000000..20f4aa1bdaeb --- /dev/null +++ b/tests/init/neg/inner-outer/inner5.scala @@ -0,0 +1,10 @@ +class Foo { + class B { + foo(10) + } + + new B + val a = 3 // error + + def foo(x: Int) = a + x +} \ No newline at end of file diff --git a/tests/init/neg/inner-outer/inner6.scala b/tests/init/neg/inner-outer/inner6.scala new file mode 100644 index 000000000000..42b68fe2d8ea --- /dev/null +++ b/tests/init/neg/inner-outer/inner6.scala @@ -0,0 +1,23 @@ +class Parent { + class Inner1 { + val len = list.size + } + + class Inner2 { + val len = foo + } + + private val list = List(3, 5, 6) + def foo: Int = 5 +} + +class Child extends Parent { + class InnerA extends Inner1 + class InnerB extends Inner2 + + new InnerA + new InnerB + + val x = 10 // error + override def foo: Int = x * x +} diff --git a/tests/init/neg/inner-outer/inner7.scala b/tests/init/neg/inner-outer/inner7.scala new file mode 100644 index 000000000000..bdac38cc31d8 --- /dev/null +++ b/tests/init/neg/inner-outer/inner7.scala @@ -0,0 +1,16 @@ +class Parent { + class Inner1 { + val len = foo + } + + val list = List(3, 5, 6) + def foo: Int = 5 +} + +class Child extends Parent { + class InnerA extends Inner1 + + new InnerA + override val list = List(4, 5) // error + override def foo: Int = list.size +} diff --git a/tests/init/neg/inner-outer/inner9.scala b/tests/init/neg/inner-outer/inner9.scala new file mode 100644 index 000000000000..db5198ea0138 --- /dev/null +++ b/tests/init/neg/inner-outer/inner9.scala @@ -0,0 +1,20 @@ +object Flags { + class Inner { + println(b) + } + + new Flags.Inner + + val a = this.b + 3 + val b = 5 // error +} + +object Flags2 { + class Inner { + println(b) + } + + + lazy val a = 3 + val b = 5 +} diff --git a/tests/init/neg/inner-outer/trees.scala b/tests/init/neg/inner-outer/trees.scala new file mode 100644 index 000000000000..836f02e9c551 --- /dev/null +++ b/tests/init/neg/inner-outer/trees.scala @@ -0,0 +1,7 @@ +class Trees { + val a = 10 + class ValDef { counter += 1 } + class EmptyValDef extends ValDef + val theEmptyValDef = new EmptyValDef + private var counter = 0 // error +} diff --git a/tests/init/neg/misc/Properties.scala b/tests/init/neg/misc/Properties.scala new file mode 100644 index 000000000000..df4aee821f9a --- /dev/null +++ b/tests/init/neg/misc/Properties.scala @@ -0,0 +1,99 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2015, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala +package util + +import java.io.{ IOException, PrintWriter } +import java.util.jar.Attributes.{ Name => AttributeName } + +private[scala] trait PropertiesTrait { + protected def propCategory: String // specializes the remainder of the values + protected def pickJarBasedOn: Class[_] // props file comes from jar containing this + + /** The name of the properties file */ + protected val propFilename = "/" + propCategory + ".properties" + + /** The loaded properties */ + protected lazy val scalaProps: java.util.Properties = { + val props = new java.util.Properties + val stream = pickJarBasedOn getResourceAsStream propFilename + if (stream ne null) + quietlyDispose(props load stream, stream.close) + + props + } + + private def quietlyDispose(action: => Unit, disposal: => Unit) = + try { action } + finally { + try { disposal } + catch { case _: IOException => } + } + + final def propIsSet(name: String) = System.getProperty(name) != null + final def propIsSetTo(name: String, value: String) = propOrNull(name) == value + final def propOrElse(name: String, alt: String) = System.getProperty(name, alt) + final def propOrEmpty(name: String) = propOrElse(name, "") + final def propOrNull(name: String) = propOrElse(name, null) + final def propOrNone(name: String) = Option(propOrNull(name)) + final def propOrFalse(name: String) = propOrNone(name) exists (x => List("yes", "on", "true") contains x.toLowerCase) + final def setProp(name: String, value: String) = System.setProperty(name, value) + final def clearProp(name: String) = System.clearProperty(name) + + final def envOrElse(name: String, alt: String) = Option(System getenv name) getOrElse alt + final def envOrNone(name: String) = Option(System getenv name) + + final def envOrSome(name: String, alt: Option[String]) = envOrNone(name) orElse alt + + // for values based on propFilename, falling back to System properties + final def scalaPropOrElse(name: String, alt: String): String = scalaPropOrNone(name).getOrElse(alt) + final def scalaPropOrEmpty(name: String): String = scalaPropOrElse(name, "") + final def scalaPropOrNone(name: String): Option[String] = Option(scalaProps.getProperty(name)).orElse(propOrNone("scala." + name)) + + /** The numeric portion of the runtime Scala version, if this is a final + * release. If for instance the versionString says "version 2.9.0.final", + * this would return Some("2.9.0"). + * + * @return Some(version) if this is a final release build, None if + * it is an RC, Beta, etc. or was built from source, or if the version + * cannot be read. + */ + val releaseVersion = + for { + v <- scalaPropOrNone("maven.version.number") + if !(v endsWith "-SNAPSHOT") + } yield v + + /** The development Scala version, if this is not a final release. + * The precise contents are not guaranteed, but it aims to provide a + * unique repository identifier (currently the svn revision) in the + * fourth dotted segment if the running version was built from source. + * + * @return Some(version) if this is a non-final version, None if this + * is a final release or the version cannot be read. + */ + val developmentVersion = + for { + v <- scalaPropOrNone("maven.version.number") + if v endsWith "-SNAPSHOT" + ov <- scalaPropOrNone("version.number") + } yield ov + + /** Either the development or release version if known, otherwise + * the empty string. + */ + def versionNumberString = scalaPropOrEmpty("version.number") + + /** The version number of the jar this was loaded from plus "version " prefix, + * or "version (unknown)" if it cannot be determined. + */ + val versionString = "version " + scalaPropOrElse("version.number", "(unknown)") + val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2017, LAMP/EPFL and Lightbend, Inc.") +} diff --git a/tests/init/neg/misc/alias.scala b/tests/init/neg/misc/alias.scala new file mode 100644 index 000000000000..79367b36d0c5 --- /dev/null +++ b/tests/init/neg/misc/alias.scala @@ -0,0 +1,5 @@ +class Foo { + val self = this + val x = self.n + val n = 10 // error +} \ No newline at end of file diff --git a/tests/init/neg/misc/assignments.scala b/tests/init/neg/misc/assignments.scala new file mode 100644 index 000000000000..bab536e38009 --- /dev/null +++ b/tests/init/neg/misc/assignments.scala @@ -0,0 +1,26 @@ +// test for exception +object assignments { + + var a = Array(1, 2, 3) + var i = 0 + a(i) = a(i) * 2 + a(i + 1) += 1 + + class C { + var myX = 0 + def x = myX + def x_=(x: Int) = myX = x + + x = x + 1 + x *= 2 + } + + var c = new C + c.x =c.x + 1 + c.x *= 2 + + val cc = c + import cc._ + x = x + 1 + x *= 2 +} diff --git a/tests/init/neg/misc/by-name-error.scala b/tests/init/neg/misc/by-name-error.scala new file mode 100644 index 000000000000..960bbe9bf7e9 --- /dev/null +++ b/tests/init/neg/misc/by-name-error.scala @@ -0,0 +1,13 @@ +trait Foo { + def next: Foo +} + +object Foo { + implicit def foo(implicit rec: => Foo): Foo = + new Foo { def next = rec } +} + +class A { + val foo = implicitly[Foo] // error + assert(foo eq foo.next) +} \ No newline at end of file diff --git a/tests/init/neg/misc/by-name-inline.scala b/tests/init/neg/misc/by-name-inline.scala new file mode 100644 index 000000000000..ff91d04f47f8 --- /dev/null +++ b/tests/init/neg/misc/by-name-inline.scala @@ -0,0 +1,13 @@ +trait Foo { + def next: Foo +} + +object Foo { + inline implicit def foo(implicit rec: => Foo): Foo = + new Foo { def next = rec } +} + +class A { + val foo = implicitly[Foo] + assert(foo eq foo.next) +} \ No newline at end of file diff --git a/tests/init/neg/misc/constant.scala b/tests/init/neg/misc/constant.scala new file mode 100644 index 000000000000..042c264171ba --- /dev/null +++ b/tests/init/neg/misc/constant.scala @@ -0,0 +1,4 @@ +class A { + final val a = b + final val b = 4 +} \ No newline at end of file diff --git a/tests/init/neg/misc/enum.scala b/tests/init/neg/misc/enum.scala new file mode 100644 index 000000000000..0d31410296df --- /dev/null +++ b/tests/init/neg/misc/enum.scala @@ -0,0 +1,3 @@ +enum MatchCheck { + case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom +} diff --git a/tests/init/neg/misc/escape1.scala b/tests/init/neg/misc/escape1.scala new file mode 100644 index 000000000000..588f0933cfc3 --- /dev/null +++ b/tests/init/neg/misc/escape1.scala @@ -0,0 +1,8 @@ +class Foo { + val a = Foo.bar(this) // error + val b = "hello" +} + +object Foo { + def bar(foo: Foo) = foo.b + ", world" +} diff --git a/tests/init/neg/misc/exception1.scala b/tests/init/neg/misc/exception1.scala new file mode 100644 index 000000000000..88115e6dd0e9 --- /dev/null +++ b/tests/init/neg/misc/exception1.scala @@ -0,0 +1,14 @@ +// check exception + +class Box(var x: Int) + +trait Parent { + def f: Box +} + +abstract class Child extends Parent { + def m: Int = { + f.x = 10 + 10 + } +} diff --git a/tests/init/neg/misc/explosion.scala b/tests/init/neg/misc/explosion.scala new file mode 100644 index 000000000000..a519983f0daa --- /dev/null +++ b/tests/init/neg/misc/explosion.scala @@ -0,0 +1,379 @@ +import scala.language.implicitConversions + +object Flags { + + /** A FlagSet represents a set of flags. Flags are encoded as follows: + * The first two bits indicate whether a flagset applies to terms, + * to types, or to both. Bits 2..63 are available for properties + * and can be doubly used for terms and types. + */ + case class FlagSet(val bits: Long) extends AnyVal { + + /** The union of this flag set and the given flag set + * Combining two FlagSets with `|` will give a FlagSet + * that has the intersection of the applicability to terms/types + * of the two flag sets. It is checked that the intersection is not empty. + */ + def | (that: FlagSet): FlagSet = + if (bits == 0) that + else if (that.bits == 0) this + else { + val tbits = bits & that.bits & KINDFLAGS + if (tbits == 0) + assert(false, s"illegal flagset combination: $this and $that") + FlagSet(tbits | ((this.bits | that.bits) & ~KINDFLAGS)) + } + + /** The intersection of this flag set and the given flag set */ + def & (that: FlagSet) = FlagSet(bits & that.bits) + + /** This flag set with all flags transposed to be type flags */ + def toTypeFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TYPES) + + /** This flag set with all flags transposed to be term flags */ + def toTermFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TERMS) + + /** This flag set with all flags transposed to be common flags */ + def toCommonFlags = if (bits == 0) this else FlagSet(bits | KINDFLAGS) + + /** The number of non-kind flags in this set */ + def numFlags: Int = java.lang.Long.bitCount(bits & ~KINDFLAGS) + } + + /** A class representing flag sets that should be tested + * conjunctively. I.e. for a flag conjunction `fc`, + * `x is fc` tests whether `x` contains all flags in `fc`. + */ + case class FlagConjunction(bits: Long) { + override def toString = FlagSet(bits).toString + } + + private final val TYPESHIFT = 2 + private final val TERMindex = 0 + private final val TYPEindex = 1 + private final val TERMS = 1 << TERMindex + private final val TYPES = 1 << TYPEindex + private final val KINDFLAGS = TERMS | TYPES + + private final val FirstFlag = 2 + private final val FirstNotPickledFlag = 48 + private final val MaxFlag = 63 + + private val flagName = Array.fill(64, 2)("") + + private def isDefinedAsFlag(idx: Int) = flagName(idx) exists (_.nonEmpty) + + /** The flag set containing all defined flags of either kind whose bits + * lie in the given range + */ + private def flagRange(start: Int, end: Int) = + FlagSet((KINDFLAGS.toLong /: (start until end)) ((bits, idx) => + if (isDefinedAsFlag(idx)) bits | (1L << idx) else bits)) + + /** The flag with given index between 2 and 63 which applies to terms. + * Installs given name as the name of the flag. */ + private def termFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + FlagSet(TERMS | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to types. + * Installs given name as the name of the flag. */ + private def typeFlag(index: Int, name: String): FlagSet = { + flagName(index)(TYPEindex) = name + FlagSet(TYPES | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to both terms and types + * Installs given name as the name of the flag. */ + private def commonFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + flagName(index)(TYPEindex) = name + FlagSet(TERMS | TYPES | (1L << index)) + } + + /** The union of all flags in given flag set */ + def union(flagss: FlagSet*): FlagSet = { + var flag = EmptyFlags + for (f <- flagss) + flag |= f + flag + } + + def commonFlags(flagss: FlagSet*) = union(flagss.map(_.toCommonFlags): _*) + + /** The empty flag set */ + final val EmptyFlags = FlagSet(0) + + /** The undefined flag set */ + final val UndefinedFlags = FlagSet(~KINDFLAGS) + + // Available flags: + + /** Labeled with `private` modifier */ + final val Private = commonFlag(2, "private") + final val PrivateTerm = Private.toTermFlags + final val PrivateType = Private.toTypeFlags + + /** Labeled with `protected` modifier */ + final val Protected = commonFlag(3, "protected") + + /** Labeled with `override` modifier */ + final val Override = commonFlag(4, "override") + + /** A declared, but not defined member */ + final val Deferred = commonFlag(5, "") + final val DeferredTerm = Deferred.toTermFlags + final val DeferredType = Deferred.toTypeFlags + + /** Labeled with `final` modifier */ + final val Final = commonFlag(6, "final") + + /** A method symbol. */ + final val Method = termFlag(7, "") + final val HigherKinded = typeFlag(7, "") + + /** A (term or type) parameter to a class or method */ + final val Param = commonFlag(8, "") + final val TermParam = Param.toTermFlags + final val TypeParam = Param.toTypeFlags + + /** Labeled with `implicit` modifier (implicit value) */ + final val ImplicitCommon = commonFlag(9, "implicit") + final val Implicit = ImplicitCommon.toTermFlags + + /** Labeled with `lazy` (a lazy val). */ + final val Lazy = termFlag(10, "lazy") + + /** A trait */ + final val Trait = typeFlag(10, "") + + final val LazyOrTrait = Lazy.toCommonFlags + + /** A value or variable accessor (getter or setter) */ + final val Accessor = termFlag(11, "") + + /** Labeled with `sealed` modifier (sealed class) */ + final val Sealed = typeFlag(11, "sealed") + + final val AccessorOrSealed = Accessor.toCommonFlags + + /** A mutable var */ + final val Mutable = termFlag(12, "mutable") + + /** Symbol is local to current class (i.e. private[this] or protected[this] + * pre: Private or Protected are also set + */ + final val Local = commonFlag(13, "") + + /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), + * or an accessor of such a field. + */ + final val ParamAccessor = termFlag(14, "") + + /** A value or class implementing a module */ + final val Module = commonFlag(15, "module") + final val ModuleVal = Module.toTermFlags + final val ModuleClass = Module.toTypeFlags + + /** A value or class representing a package */ + final val Package = commonFlag(16, "") + final val PackageVal = Package.toTermFlags + final val PackageClass = Package.toTypeFlags + + /** A case class or its companion object */ + final val Case = commonFlag(17, "case") + final val CaseClass = Case.toTypeFlags + final val CaseVal = Case.toTermFlags + + /** A compiler-generated symbol, which is visible for type-checking + * (compare with artifact) + */ + final val Synthetic = commonFlag(18, "") + + /** Labelled with `inline` modifier */ + final val Inline = commonFlag(19, "inline") + + /** A covariant type variable / an outer accessor */ + final val CovariantOrOuter = commonFlag(20, "") + final val Covariant = typeFlag(20, "") + final val OuterAccessor = termFlag(20, "") + + /** A contravariant type variable / a label method */ + final val ContravariantOrLabel = commonFlag(21, "") + final val Contravariant = typeFlag(21, "") + final val Label = termFlag(21, "