diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 8f4ab656881b..700e3c68d219 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -58,7 +58,8 @@ class Compiler { new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars 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 + new CheckStatic, // Check restrictions that apply to @static members + 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/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index e5e73518aa19..7eec52abf285 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -630,7 +630,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** An extractor for def of a closure contained the block of the closure. */ object closureDef { def unapply(tree: Tree)(implicit ctx: Context): Option[DefDef] = tree match { - case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, closure: Closure) => + case Block((meth : DefDef) :: Nil, closure: Closure) if meth.symbol == closure.meth.symbol => Some(meth) case Block(Nil, expr) => unapply(expr) 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/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/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 4ee962aaa915..0759fd2e1080 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -461,8 +461,9 @@ object Symbols { */ def retainsDefTree(implicit ctx: Context): Boolean = ctx.settings.YretainTrees.value || - denot.owner.isTerm || // no risk of leaking memory after a run for these - denot.isOneOf(InlineOrProxy) // need to keep inline info + denot.owner.isTerm || // no risk of leaking memory after a run for these + denot.isOneOf(InlineOrProxy) || // need to keep inline info + ctx.settings.YcheckInit.value // initialization check /** The last denotation of this symbol */ private var lastDenot: SymDenotation = _ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala new file mode 100644 index 000000000000..497b6e3f7314 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -0,0 +1,63 @@ +package dotty.tools.dotc +package transform +package init + + +import dotty.tools.dotc._ +import ast.tpd + +import dotty.tools.dotc.core._ +import Contexts.Context +import Types._ + +import dotty.tools.dotc.transform._ +import MegaPhase._ + + +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(Pickler.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 + 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 (instantiable) { + 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/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala new file mode 100644 index 000000000000..0fb978c3a570 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -0,0 +1,379 @@ +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 reporting.trace +import config.Printers.init + +import Effects._, Potentials._, Summary._, Util._, Errors._ + +object Checking { + /** The checking state + * + * Why `visited` is a set of effects 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], // 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): State = { + visited += eff + copy(path = this.path :+ eff.source) + } + } + + 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)(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, 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)(theEnv.withOwner(vdef.symbol)) + theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs)) + if (!vdef.symbol.is(Flags.Lazy)) { + checkEffectsIn(effs, cls) + traceIndented(vdef.symbol.show + " initialized", init) + state.fieldsInited += vdef.symbol + } + + case tree => + val (_, effs) = Summarization.analyze(tree) + checkEffectsIn(effs, cls) + } + } + + // 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 + + def checkCtor(ctor: Symbol, tp: Type, source: Tree)(implicit ctx: Context): Unit = { + val cls = ctor.owner + val classDef = cls.defTree + if (!classDef.isEmpty) { + if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef]) + else checkSecondaryConstructor(ctor) + } + else if (!cls.isOneOf(Flags.EffectivelyOpenFlags)) + 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(_, parent) => + val (ctor, _, _) = decomposeCall(parent) + checkCtor(ctor.symbol, parent.tpe, tree) + + case tree @ Apply(Block(_, parent), _) => + val (ctor, _, _) = decomposeCall(parent) + checkCtor(ctor.symbol, tree.tpe, tree) + + case parent : Apply => + val (ctor, _, argss) = decomposeCall(parent) + checkCtor(ctor.symbol, parent.tpe, parent) + + case ref => + val cls = ref.tpe.classSymbol.asClass + if (!state.parentsInited.contains(cls) && cls.primaryConstructor.exists) + checkCtor(cls.primaryConstructor, ref.tpe, ref) + } + + // check class body + tpl.body.foreach { checkClassBodyStat(_) } + } + + def checkSecondaryConstructor(ctor: Symbol)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) { + val Block(ctorCall :: stats, expr) = ctor.defTree.asInstanceOf[DefDef].rhs + val cls = ctor.owner.asClass + + traceOp("check ctor: " + ctorCall.show, init) { + val ctor = ctorCall.symbol + if (ctor.isPrimaryConstructor) + checkClassBody(cls.defTree.asInstanceOf[TypeDef]) + else + checkSecondaryConstructor(ctor) + } + + (stats :+ expr).foreach { stat => + val (_, effs) = Summarization.analyze(stat)(theEnv.withOwner(ctor)) + checkEffectsIn(effs, cls) + } + } + + private def checkEffectsIn(effs: Effects, cls: ClassSymbol)(implicit state: State): Unit = traceOp("checking effects " + Effects.show(effs), init) { + val rebased = Effects.asSeenFrom(effs, ThisRef(state.thisClass)(null), cls, Potentials.empty) + for { + eff <- rebased + error <- check(eff) + } error.report + } + + private def check(eff: Effect)(implicit state: State): Errors = + if (state.visited.contains(eff)) Errors.empty else trace("checking effect " + eff.show, init, errs => Errors.show(errs.asInstanceOf[Errors])) { + implicit val state2: State = state.withVisited(eff) + + eff match { + case Promote(pot) => + pot match { + case pot @ ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + PromoteThis(pot, eff.source, state2.path).toErrors + + case _: Cold => + PromoteCold(eff.source, state2.path).toErrors + + case pot @ Warm(cls, outer) => + PromoteWarm(pot, eff.source, state2.path).toErrors + + case Fun(pots, effs) => + val errs1 = effs.flatMap { check(_) } + val errs2 = pots.flatMap { pot => check(Promote(pot)(eff.source))(state.copy(path = Vector.empty)) } + if (errs1.nonEmpty || errs2.nonEmpty) + UnsafePromotion(pot, eff.source, state2.path, errs1 ++ errs2).toErrors + else + Errors.empty + + case pot => + val (pots, effs) = expand(pot) + val effs2 = pots.map(Promote(_)(eff.source)) + (effs2 ++ effs).flatMap(check(_)) + } + + case FieldAccess(pot, field) => + + pot match { + case ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolve(cls, field) + if (target.is(Flags.Lazy)) check(MethodCall(pot, target)(eff.source)) + else if (!state.fieldsInited.contains(target)) AccessNonInit(target, state2.path).toErrors + else Errors.empty + + case SuperRef(ThisRef(cls), supercls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolveSuper(cls, supercls, field) + if (target.is(Flags.Lazy)) check(MethodCall(pot, target)(eff.source)) + else if (!state.fieldsInited.contains(target)) AccessNonInit(target, state2.path).toErrors + else Errors.empty + + case Warm(cls, outer) => + // all fields of warm values are initialized + val target = resolve(cls, field) + if (target.is(Flags.Lazy)) check(MethodCall(pot, target)(eff.source)) + else Errors.empty + + case _: Cold => + AccessCold(field, eff.source, state2.path).toErrors + + case Fun(pots, effs) => + throw new Exception("Unexpected effect " + eff.show) + + case pot => + val (pots, effs) = expand(pot) + val effs2 = pots.map(FieldAccess(_, field)(eff.source)) + (effs2 ++ effs).flatMap(check(_)) + + } + + case MethodCall(pot, sym) => + pot match { + case thisRef @ ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolve(cls, sym) + if (!target.isOneOf(Flags.Method | Flags.Lazy)) + check(FieldAccess(pot, target)(eff.source)) + else if (target.isInternal) { + val effs = thisRef.effectsOf(target) + effs.flatMap { check(_) } + } + else CallUnknown(target, eff.source, state2.path).toErrors + + case SuperRef(thisRef @ ThisRef(cls), supercls) => + 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)) + else if (target.isInternal) { + val effs = thisRef.effectsOf(target) + effs.flatMap { check(_) } + } + else CallUnknown(target, eff.source, state2.path).toErrors + + case warm @ Warm(cls, outer) => + val target = resolve(cls, sym) + + if (target.isInternal) { + val effs = warm.effectsOf(target) + effs.flatMap { check(_) } + } + else if (!sym.isConstructor) CallUnknown(target, eff.source, state2.path).toErrors + else Errors.empty + + case _: Cold => + CallCold(sym, eff.source, state2.path).toErrors + + case Fun(pots, effs) => + // TODO: assertion might be false, due to SAM + if (sym.name.toString == "apply") effs.flatMap { check(_) } + else Errors.empty + // curried, tupled, toString are harmless + + case pot => + val (pots, effs) = expand(pot) + val effs2 = pots.map(MethodCall(_, sym)(eff.source)) + (effs2 ++ effs).flatMap(check(_)) + } + } + } + + private def expand(pot: Potential)(implicit state: State): Summary = trace("expand " + pot.show, init, sum => Summary.show(sum.asInstanceOf[Summary])) { + pot match { + case MethodReturn(pot1, sym) => + pot1 match { + case thisRef @ ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolve(cls, sym) + if (target.isInternal) (thisRef.potentialsOf(target), Effects.empty) + else Summary.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), Effects.empty) + else Summary.empty // warning already issued in call effect + + + case Fun(pots, effs) => + val name = sym.name.toString + if (name == "apply") (pots, Effects.empty) + else if (name == "tupled") (Set(pot1), Effects.empty) + else if (name == "curried") { + val arity = defn.functionArity(sym.info.finalResultType) + val pots = (1 until arity).foldLeft(Set(pot1)) { (acc, i) => Set(Fun(acc, Effects.empty)(pot1.source)) } + (pots, Effects.empty) + } + else Summary.empty + + case warm : Warm => + val target = resolve(warm.classSymbol, sym) + if (target.isInternal) (warm.potentialsOf(target), Effects.empty) + else Summary.empty // warning already issued in call effect + + case _: Cold => + Summary.empty // error already reported, ignore + + case _ => + val (pots, effs) = expand(pot1) + val (pots2, effs2) = pots.select(sym, pot.source) + (pots2, effs ++ effs2) + } + + case FieldReturn(pot1, sym) => + pot1 match { + case thisRef @ ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + val target = resolve(cls, sym) + if (sym.isInternal) (thisRef.potentialsOf(target), Effects.empty) + else (Cold()(pot.source).toPots, Effects.empty) + + 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), Effects.empty) + else (Cold()(pot.source).toPots, Effects.empty) + + case _: Fun => + throw new Exception("Unexpected code reached") + + case warm: Warm => + val target = resolve(warm.classSymbol, sym) + if (target.isInternal) (warm.potentialsOf(target), Effects.empty) + else (Cold()(pot.source).toPots, Effects.empty) + + case _: Cold => + Summary.empty // error already reported, ignore + + case _ => + val (pots, effs) = expand(pot1) + val (pots2, effs2) = pots.select(sym, pot.source) + (pots2, effs ++ effs2) + } + + case Outer(pot1, cls) => + pot1 match { + case ThisRef(cls) => + assert(cls == state.thisClass, "unexpected potential " + pot.show) + + Summary.empty + + case _: Fun => + throw new Exception("Unexpected code reached") + + case warm: Warm => + (warm.outerFor(cls), Effects.empty) + + case _: Cold => + throw new Exception("Unexpected code reached") + + case _ => + val (pots, effs) = expand(pot1) + val pots2 = pots.map { Outer(_, cls)(pot.source): Potential } + (pots2, effs) + } + + case _: ThisRef | _: Fun | _: Warm | _: Cold => + (Set(pot), Effects.empty) + + case SuperRef(pot1, supercls) => + val (pots, effs) = expand(pot1) + val pots2 = pots.map { SuperRef(_, supercls)(pot.source): Potential } + (pots2, effs) + } + } +} 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..fecaee620148 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -0,0 +1,84 @@ +package dotty.tools.dotc +package transform +package init + +import ast.tpd._ +import reporting.trace +import config.Printers.init +import core.Types._ +import core.Symbols._ +import core.Contexts._ + +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(", ") + + /** Effects that are related to safe initialization */ + sealed trait Effect { + def size: Int + def show(implicit ctx: Context): String + def source: Tree + } + + /** An effect means that a value that's possibly under initialization + * is promoted from the initializing world to the fully-initialized world. + * + * Essentially, this effect enforces that the object pointed to by + * `potential` is fully initialized. + * + * This effect is trigger in several scenarios: + * - a potential is used as arguments to method calls or new-expressions + * - a potential is assigned (not initialize) to a field + * - the selection chain on a potential is too long + */ + case class Promote(potential: Potential)(val source: Tree) extends Effect { + def size: Int = potential.size + def show(implicit ctx: Context): String = + potential.show + "↑" + } + + /** Field access, `a.f` */ + case class FieldAccess(potential: Potential, field: Symbol)(val source: Tree) extends Effect { + assert(field != NoSymbol) + + def size: Int = potential.size + def show(implicit ctx: Context): String = + potential.show + "." + field.name.show + "!" + } + + /** Method call, `a.m()` */ + case class MethodCall(potential: Potential, method: Symbol)(val source: Tree) extends Effect { + assert(method != NoSymbol) + + def size: Int = potential.size + def show(implicit ctx: Context): String = potential.show + "." + method.name.show + "!" + } + + // ------------------ 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 = + trace(eff.show + " asSeenFrom " + thisValue.show + ", current = " + currentClass.show + ", outer = " + Potentials.show(outer), init, effs => show(effs.asInstanceOf[Effects])) { eff match { + case Promote(pot) => + Potentials.asSeenFrom(pot, thisValue, currentClass, outer).promote(eff.source) + + case FieldAccess(pot, field) => + Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot => + FieldAccess(pot, field)(eff.source) + } + + case MethodCall(pot, sym) => + 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)) +} \ No newline at end of file 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..66411a964a18 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Env.scala @@ -0,0 +1,57 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import Types._ +import Symbols._ +import Decorators._ + +import ast.Trees._ +import ast.tpd + +import reporting.trace +import config.Printers.init + +import scala.collection.mutable + +import Effects._, Potentials._, Summary._ + +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.requiredClass("scala.runtime.EnumValues").requiredMethod("register"), + defn.Any_getClass, + defn.Any_isInstanceOf, + defn.Object_eq, + defn.Object_ne, + defn.Object_synchronized + ) + + 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) + else trace("summary for " + cls.show, init, s => s.asInstanceOf[ClassSummary].show) { + val summary = Summarization.classSummary(cls) + summaryCache(cls) = summary + summary + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala new file mode 100644 index 000000000000..edca011f42b0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -0,0 +1,119 @@ +package dotty.tools.dotc +package transform +package init + +import ast.tpd._ + +import core._ +import Decorators._, printing.SyntaxHighlighting +import Types._, Symbols._, Contexts._ +import util.SourcePosition + +import Effects._, Potentials._ + +object Errors { + type Errors = Set[Error] + val empty: Errors = Set.empty + + def show(errs: Errors)(implicit ctx: Context): String = + errs.map(_.show).mkString(", ") + + sealed trait Error { + def source: Tree + def trace: Vector[Tree] + def show(implicit ctx: Context): String + + def report(implicit ctx: Context): Unit = + ctx.warning(show + stacktrace, source.sourcePos) + + def toErrors: Errors = Set(this) + + def stacktrace(implicit ctx: Context): String = if (trace.isEmpty) "" else " Calling trace:\n" + { + var indentCount = 0 + var last: String = "" + val sb = new StringBuilder + trace.foreach { tree => + indentCount += 1 + val pos = tree.sourcePos + val prefix = s"${ " " * indentCount }-> " + val line = + if pos.source.exists then + val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" + val code = SyntaxHighlighting.highlight(pos.lineContent.trim) + i"$code\t$loc" + else + tree.show + + if (last != line) sb.append(prefix + line + "\n") + + last = line + } + sb.toString + } + + /** Flatten UnsafePromotion errors + */ + def flatten: Errors = this match { + case unsafe: UnsafePromotion => unsafe.errors.flatMap(_.flatten) + case _ => Set(this) + } + } + + /** Access non-initialized field */ + case class AccessNonInit(field: Symbol, trace: Vector[Tree]) extends Error { + def source: Tree = trace.last + def show(implicit ctx: Context): String = + "Access non-initialized field " + field.name.show + "." + + override def report(implicit ctx: Context): Unit = ctx.error(show + stacktrace, field.sourcePos) + } + + /** Promote `this` under initialization to fully-initialized */ + case class PromoteThis(pot: ThisRef, source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = "Promote the value under initialization to fully-initialized." + } + + /** Promote `this` under initialization to fully-initialized */ + case class PromoteWarm(pot: Warm, source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = + "Promoting the value under initialization to fully-initialized." + } + + /** Promote a cold value under initialization to fully-initialized */ + case class PromoteCold(source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = + "Promoting the value " + source.show + " to fully-initialized while it is under initialization" + "." + } + + case class AccessCold(field: Symbol, source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = + "Access field " + source.show + " on a value with an unknown initialization status" + "." + } + + case class CallCold(meth: Symbol, source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = + "Call method " + source.show + " on a value with an unknown initialization" + "." + } + + case class CallUnknown(meth: Symbol, source: Tree, trace: Vector[Tree]) extends Error { + def show(implicit ctx: Context): String = + "Calling the external method " + meth.show + " may cause initialization errors" + "." + } + + /** Promote a value under initialization to fully-initialized */ + case class UnsafePromotion(pot: Potential, source: Tree, trace: Vector[Tree], errors: Errors) extends Error { + assert(errors.nonEmpty) + + override def report(implicit ctx: Context): Unit = ctx.warning(show, source.sourcePos) + + def show(implicit ctx: Context): String = { + var index = 0 + "Promoting the value to fully-initialized is unsafe.\n" + stacktrace + + "\nThe unsafe promotion may cause the following problem(s):\n" + + (errors.flatMap(_.flatten).map { error => + index += 1 + s"\n$index. " + error.show + error.stacktrace + }.mkString) + } + } +} \ 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..9098a0d2574d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -0,0 +1,222 @@ +package dotty.tools.dotc +package transform +package init + +import scala.collection.mutable + +import ast.tpd._ +import reporting.trace +import config.Printers.init + +import core._ +import Types._, Symbols._, Contexts._ + +import Effects._, Summary._ + +object Potentials { + type Potentials = Set[Potential] + val empty: Potentials = Set.empty + + def show(pots: Potentials)(implicit ctx: Context): String = + pots.map(_.show).mkString(", ") + + /** A potential represents an aliasing of a value that is possibly under initialization */ + sealed trait Potential { + def size: Int + def show(implicit ctx: Context): String + def source: Tree + } + + /** The object pointed by `C.this` */ + 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 = trace("effects of " + sym.show, init, r => Effects.show(r.asInstanceOf)) { + 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 = trace("potentials of " + sym.show, init, r => Potentials.show(r.asInstanceOf)) { + val cls = sym.owner.asClass + val pots = env.summaryOf(cls).potentialsOf(sym) + Potentials.asSeenFrom(pots, this, cls, Potentials.empty) + } + } + + /** The object pointed by `C.super.this`, mainly used for override resolution */ + case class SuperRef(pot: Potential, supercls: ClassSymbol)(val source: Tree) extends Potential { + val size: Int = 1 + 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 potential for `this` of the enclosing class + */ + case class Warm(classSymbol: ClassSymbol, outer: Potential)(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = "Warm[" + classSymbol.show + ", outer = " + outer.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 = trace("effects of " + sym.show, init, r => Effects.show(r.asInstanceOf)) { + 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 = trace("potentials of " + sym.show, init, r => Potentials.show(r.asInstanceOf)) { + 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) + else if (cls `eq` classSymbol) outer.toPots + else { + val bottomClsSummary = env.summaryOf(classSymbol) + val objPart = ObjectPart(this, classSymbol, outer.toPots, 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 + "]" + } + + /** The object pointed by `this.f` */ + case class FieldReturn(potential: Potential, field: Symbol)(val source: Tree) extends Potential { + assert(field != NoSymbol) + + def size: Int = potential.size + 1 + def show(implicit ctx: Context): String = potential.show + "." + field.name.show + } + + /** The object returned by `this.m()` */ + case class MethodReturn(potential: Potential, method: Symbol)(val source: Tree) extends Potential { + assert(method != NoSymbol) + + def size: Int = potential.size + 1 + def show(implicit ctx: Context): String = potential.show + "." + method.name.show + } + + /** The object whose initialization status is unknown */ + case class Cold()(val source: Tree) extends Potential { + def size: Int = 1 + def show(implicit ctx: Context): String = "Cold" + } + + /** A function when called will produce the `effects` and return the `potentials` */ + 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(";") + "]" + } + + // ------------------ operations on potentials ------------------ + + def (pot: Potential) toPots: Potentials = Potentials.empty + pot + + def (ps: Potentials) select (symbol: Symbol, source: Tree)(implicit ctx: Context): Summary = + ps.foldLeft(Summary.empty) { case ((pots, effs), pot) => + // max potential length + // TODO: it can be specified on a project basis via compiler options + if (pot.size > 2) + (pots, effs + Promote(pot)(source)) + else if (symbol.isConstructor) + (pots + pot, effs + MethodCall(pot, symbol)(source)) + else if (symbol.isOneOf(Flags.Method | Flags.Lazy)) + ( + pots + MethodReturn(pot, symbol)(source), + effs + MethodCall(pot, symbol)(source) + ) + else + (pots + FieldReturn(pot, symbol)(source), effs + FieldAccess(pot, symbol)(source)) + } + + def (ps: Potentials) promote(source: Tree): Effects = ps.map(Promote(_)(source)) + + def asSeenFrom(pot: Potential, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Potentials = + trace(pot.show + " asSeenFrom " + thisValue.show + ", current = " + currentClass.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) } + + 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) + thisValue.toPots + else if (currentClass.is(Flags.Package)) + Potentials.empty + else { + val outerCls = currentClass.owner.enclosingClass.asClass + outer.flatMap { out => + asSeenFrom(pot, out, outerCls, Outer(out, outerCls)(out.source).toPots) + } + } + + case Fun(pots, effs) => + val pots1 = Potentials.asSeenFrom(pots, thisValue, currentClass, outer) + val effs1 = Effects.asSeenFrom(effs, thisValue, currentClass, outer) + Fun(pots1, effs1)(pot.source).toPots + + 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) + outer3.map { Warm(cls, _)(pot.source) } + + case _: Cold => + pot.toPots + + 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)) +} \ No newline at end of file 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..4c4b2f480273 --- /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(Pickler.name) + + 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) + 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" +} 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..64f1312652bb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -0,0 +1,345 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import Decorators._ +import StdNames._ +import Symbols._ +import Constants.Constant +import Types._ + +import ast.tpd._ +import config.Printers.init +import reporting.trace + +import Effects._, Potentials._, Summary._, Util._ + +object Summarization { + + /** 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 env: Env): Summary = + trace("summarizing " + expr.show, init, s => Summary.show(s.asInstanceOf[Summary])) { + val summary: Summary = expr match { + case Ident(nme.WILDCARD) => + // TODO: disallow `var x: T = _` + Summary.empty + + 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 thisRef = ThisRef(thisTp.widen.classSymbol.asClass)(supert) + val pots = superTp.classSymbols.map { cls => SuperRef(thisRef, cls.asClass)(supert) } + (pots.toSet, Effects.empty) + + case Select(qualifier, name) => + val (pots, effs) = analyze(qualifier) + if (env.ignoredMethods.contains(expr.symbol)) (Potentials.empty, effs) + else if (!expr.symbol.exists) { // polymorphic function apply and structural types + (Potentials.empty, pots.promote(expr) ++ effs) + } + else { + val (pots2, effs2) = pots.select(expr.symbol, expr) + (pots2, effs ++ effs2) + } + + case _: This => + // 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) + val ignoredCall = env.ignoredMethods.contains(expr.symbol) + + val argTps = fun.tpe.widen match + case mt: MethodType => mt.paramInfos + + val res = args.zip(argTps).foldLeft(summary) { case (sum, (arg, argTp)) => + val (pots1, effs1) = analyze(arg) + if (ignoredCall) sum.withEffs(effs1) + else if (argTp.isInstanceOf[ExprType]) sum + Promote(Fun(pots1, effs1)(arg))(arg) + else sum.withEffs(pots1.promote(arg) ++ effs1) + } + + if (ignoredCall) (Potentials.empty, res._2) + else res + + case TypeApply(fun, args) => + analyze(fun) + + case Literal(const) => + Summary.empty + + case New(tpt) => + def typeRefOf(tp: Type): TypeRef = tp.dealias.typeConstructor match { + case tref: TypeRef => tref + case hklambda: HKTypeLambda => typeRefOf(hklambda.resType) + } + + val tref = typeRefOf(tpt.tpe) + val cls = tref.classSymbol.asClass + // local class may capture, thus we need to track it + 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 { + assert(pots.size == 1) + (Warm(cls, pots.head)(expr).toPots, effs) + } + } + + case Typed(expr, tpt) => + if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Summary.empty + else analyze(expr) + + case NamedArg(name, arg) => + analyze(arg) + + case Assign(lhs, rhs) => + val (pots, effs) = analyze(rhs) + (Potentials.empty, pots.promote(expr) ++ effs) + + case closureDef(ddef) => // must be before `Block` + val (pots, effs) = analyze(ddef.rhs) + Summary.empty + Fun(pots, effs)(expr) + + 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.promote(selector) ++ effs)) { (acc, cas) => + acc union analyze(cas.body) + } + + // case CaseDef(pat, guard, body) => + // Summary.empty + + case Return(expr, from) => + val (pots, effs) = analyze(expr) + (Potentials.empty, effs ++ pots.promote(expr)) + + case WhileDo(cond, body) => + // for lazy fields, the translation may result in `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.promote(expr) ++ effs1 ++ effs + } + (Potentials.empty, effsAll) + + case Inlined(call, bindings, expansion) => + val effs = bindings.foldLeft(Effects.empty) { (acc, mdef) => acc ++ analyze(mdef)._2 } + analyze(expansion).withEffs(effs) + + case vdef : ValDef => + lazy val (pots, effs) = analyze(vdef.rhs) + + if (vdef.symbol.owner.isClass) + (Potentials.empty, if (vdef.symbol.is(Flags.Lazy)) Effects.empty else effs) + else + (Potentials.empty, pots.promote(vdef) ++ effs) + + case Thicket(List()) => + // possible in try/catch/finally, see tests/crash/i6914.scala + Summary.empty + + case ddef : DefDef => + lazy val (pots, effs) = analyze(ddef.rhs) + + if (ddef.symbol.owner.isClass) Summary.empty + else (Potentials.empty, pots.promote(ddef) ++ effs) + + case _: TypeDef => + Summary.empty + + case _: Import => + Summary.empty + + case _ => + throw new Exception("unexpected tree: " + expr.show) + } + + if (env.isAlwaysInitialized(expr.tpe)) (Potentials.empty, summary._2) + else 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 _: ConstantType => + Summary.empty + + case tmref: TermRef if tmref.prefix == NoPrefix => + Summary.empty + + case tmref: TermRef => + val (pots, effs) = analyze(tmref.prefix, source) + 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.tref.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 + + case _ => + throw new Exception("unexpected type: " + tp.show) + } + + if (env.isAlwaysInitialized(tp)) (Potentials.empty, summary._2) + else summary + } + + 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)) + } + + def analyzeField(sym: Symbol)(implicit env: Env): Summary = { + val vdef = sym.defTree.asInstanceOf[ValDef] + analyze(vdef.rhs)(env.withOwner(sym)) + } + + /** Summarize secondary constructors or class body */ + 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 cls = ctor.owner.asClass + val tpl = ctor.owner.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] + val effs = analyze(Block(tpl.body, unitLiteral))._2 + + def parentArgEffsWithInit(stats: List[Tree], ctor: Symbol, source: Tree): Effects = + val initCall = MethodCall(ThisRef(cls)(source), ctor)(source) + stats.foldLeft(Set(initCall)) { (acc, stat) => + val (_, effs) = Summarization.analyze(stat) + acc ++ effs + } + + val effsAll = tpl.parents.foldLeft(effs) { (effs, parent) => + effs ++ (parent match { + case tree @ Block(stats, parent) => + val (ctor @ Select(qual, _), _, argss) = decomposeCall(parent) + parentArgEffsWithInit(qual :: stats ++ argss.flatten, ctor.symbol, tree) + + case tree @ Apply(Block(stats, parent), args) => + val (ctor @ Select(qual, _), _, argss) = decomposeCall(parent) + parentArgEffsWithInit(qual :: stats ++ args ++ argss.flatten, ctor.symbol, tree) + + case parent : Apply => + val (ctor @ Select(qual, _), _, argss) = decomposeCall(parent) + parentArgEffsWithInit(qual :: argss.flatten, ctor.symbol, parent) + + case ref => + val tref: TypeRef = ref.tpe.typeConstructor.asInstanceOf + val cls = tref.classSymbol.asClass + if (cls == defn.AnyClass || cls == defn.AnyValClass) Effects.empty + else { + val ctor = cls.primaryConstructor + Summarization.analyze(tref.prefix, ref)._2 + + MethodCall(ThisRef(cls)(ref), ctor)(ref) + } + }) + } + + (Potentials.empty, effsAll) + } + else { + val ddef = ctor.defTree.asInstanceOf[DefDef] + analyze(ddef.rhs)(env.withOwner(ctor)) + } + } + + def classSummary(cls: ClassSymbol)(implicit env: Env): ClassSummary = + def extractParentOuters(parent: Type, source: Tree): (ClassSymbol, Potentials) = { + val tref = parent.typeConstructor.stripAnnots.asInstanceOf[TypeRef] + val parentCls = tref.classSymbol.asClass + if (tref.prefix != NoPrefix) + parentCls ->analyze(tref.prefix, source)._1 + else + parentCls -> analyze(cls.enclosingClass.thisType, source)._1 + } + + if (cls.defTree.isEmpty) + cls.info match { + case cinfo: ClassInfo => + val source = { + implicit val ctx2: Context = theCtx.withSource(cls.source(theCtx)) + TypeTree(cls.typeRef).withSpan(cls.span) + } + + val parentOuter = cinfo.classParents.map { extractParentOuters(_, source) }.toMap + ClassSummary(cls, parentOuter) + } + else { + val tpl = cls.defTree.asInstanceOf[TypeDef] + val parents = tpl.rhs.asInstanceOf[Template].parents + + val parentOuter = parents.map { parent => extractParentOuters(parent.tpe, parent) } + 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 new file mode 100644 index 000000000000..ec5fbd4d3523 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -0,0 +1,103 @@ +package dotty.tools.dotc +package transform +package init + +import scala.collection.mutable + +import core._ +import Contexts.Context +import Symbols._ +import reporting.trace +import config.Printers.init + +import Potentials._, Effects._, Util._ + +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 cacheFor(member: Symbol, summary: Summary)(implicit ctx: Context): Unit = { + traceIndented("cache for " + member.show + ", summary = " + Summary.show(summary), init) + assert(member.owner == currentClass, "owner = " + member.owner.show + ", current = " + currentClass.show) + summaryCache(member) = 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 = + if (member.isConstructor) + Summarization.analyzeConstructor(member) + else if (member.is(Flags.Method)) + Summarization.analyzeMethod(member) + else // field + Summarization.analyzeField(member) + + summaryCache(member) = summary + summary + } + + 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 = + "ClassSummary(" + 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, // 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 + + 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 rebased: Potentials = Potentials.asSeenFrom(pots, thisValue, currentClass, currentOuter) + 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) + "]" } + } + + def show(summary: Summary)(implicit ctx: Context): String = { + val pots = Potentials.show(summary._1) + 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..6f9dc4ada264 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -0,0 +1,41 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import Symbols._ +import config.Printers.Printer + +import annotation.tailrec + +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) + } + + def (symbol: Symbol) isInternal(implicit ctx: Context): Boolean = + !symbol.defTree.isEmpty + + def resolve(cls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = + if (sym.isEffectivelyFinal || sym.isConstructor) sym + else sym.matchingMember(cls.appliedRef) + + def resolveSuper(cls: ClassSymbol, superCls: ClassSymbol, sym: Symbol)(implicit ctx: Context): Symbol = { + // println(s"bases of $cls: " + cls.info.baseClasses) + @tailrec def loop(bcs: List[ClassSymbol]): Symbol = bcs match { + case bc :: bcs1 => + val cand = sym.matchingDecl(bcs.head, cls.thisType) + .suchThat(alt => !alt.is(Flags.Deferred)).symbol + if (cand.exists) cand else loop(bcs.tail) + case _ => + NoSymbol + } + loop(cls.info.baseClasses.dropWhile(sym.owner != _)) + } +} \ No newline at end of file diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 501f57c0049b..cf8a7cc6c91a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -294,6 +294,26 @@ 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") + compileFilesInDir("tests/init/neg/", options) + }.checkExpectedErrors() + + @Test def checkInitCrash: Unit = { + implicit val testGroup: TestGroup = TestGroup("checkInit") + val options = defaultOptions.and("-Ycheck-init") + compileFilesInDir("tests/init/crash", options) + }.checkCompile() + + @Test def checkInitPos: Unit = { + implicit val testGroup: TestGroup = TestGroup("checkInit") + val options = defaultOptions.and("-Ycheck-init", "-Xfatal-warnings") + compileFilesInDir("tests/init/pos", options) + }.checkCompile() + } object CompilationTests { diff --git a/docs/docs/reference/other-new-features/safe-initialization.md b/docs/docs/reference/other-new-features/safe-initialization.md new file mode 100644 index 000000000000..1a24c4e57632 --- /dev/null +++ b/docs/docs/reference/other-new-features/safe-initialization.md @@ -0,0 +1,311 @@ +--- +layout: doc-page +title: "Safe Initialization" +--- + +Dotty implements experimental safe initialization check, which can be enabled by the compiler option `-Ycheck-init`. + +## A Quick Glance + +To get a feel of how it works, we first show several examples below. + +### Parent-Child Interaction + +Given the following code snippet: + +``` Scala +abstract class AbstractFile { + def name: String + val extension: String = Path.extension(name) +} + +class RemoteFile(url: String) extends AbstractFile { + val localFile: String = url.hashCode + ".tmp" // error: usge of `localFile` before it's initialized + def name: String = localFile +} +``` + +The checker will report: + +``` scala +-- Warning: tests/init/neg/AbstractFile.scala:7:4 ------------------------------ +7 | val localFile: String = url.hashCode + ".tmp" // error + | ^ + | Access non-initialized field value localFile. Calling trace: + | -> val extension: String = name.substring(4) [ AbstractFile.scala:3 ] + | -> def name: String = localFile [ AbstractFile.scala:8 ] +``` + +### Inner-Outer Interaction + +Given the code below: + +``` scala +object Trees { + class ValDef { counter += 1 } + class EmptyValDef extends ValDef + val theEmptyValDef = new EmptyValDef + private var counter = 0 // error +} +``` + +The checker will report: + +``` scala +-- Warning: tests/init/neg/trees.scala:5:14 ------------------------------------ +5 | private var counter = 0 // error + | ^ + | Access non-initialized field variable counter. Calling trace: + | -> val theEmptyValDef = new EmptyValDef [ trees.scala:4 ] + | -> class EmptyValDef extends ValDef [ trees.scala:3 ] + | -> class ValDef { counter += 1 } [ trees.scala:2 ] +``` + +### Functions + +Given the code below: + +``` scala +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 +} +``` + +The checker reports: + +``` scala +-- Warning: tests/init/neg/features-high-order.scala:7:6 ----------------------- +7 | val b = "hello" // error + | ^ + |Access non-initialized field value b. Calling trace: + | -> val a = f() [ features-high-order.scala:6 ] + | -> val f: () => String = () => this.message [ features-high-order.scala:2 ] + | -> def message: String = b [ features-high-order.scala:8 ] +``` + +## Design Goals + +We establish the following design goals: + +- __Sound__: checking always terminates, and is sound for common and reasonable usage (over-approximation) +- __Expressive__: support common and reasonable initialization patterns +- __Friendly__: simple rules, minimal syntactic overhead, informative error messages +- __Modular__: modular checking, no analysis beyond project boundary +- __Fast__: instant feedback +- __Simple__: no changes to core type system, explainable by a simple theory + +By _reasonable usage_, we include the following use cases (but not restricted to them): + +- Access fields on `this` and outer `this` during initialization +- Call methods on `this` and outer `this` during initialization +- Instantiate inner class and call methods on such instances during initialization +- Capture fields in functions + +## Principles and Rules + +To achieve the goals, we uphold three fundamental principles: +_stackability_, _monotonicity_ and _scopability_. + +Stackability means that objects are initialized in stack order: if the +object `b` is created during the initialization of object `a`, then +all fields of `b` should become initialized before or at the same time +as `a`. Scala enforces this property in syntax by demanding that all +fields are initialized at the end of the primary constructor, except +for the language feature below: + +``` scala +var x: T = _ +``` + +Control effects such as exceptions may break this property, as the +following example shows: + +``` scala +class MyException(val b: B) extends Exception("") +class A { + val b = try { new B } catch { case myEx: MyException => myEx.b } + println(b.a) +} +class B { + throw new MyException(this) + val a: Int = 1 +} +``` + +In the code above, the control effect teleport the uninitialized value +wrapped in an exception. In the implementation, we avoid the problem +by ensuring that the values that are thrown must be transitively initialized. + +Monotonicity means that the initialization status of an object should +not go backward: initialized fields continue to be initialized, a +field points to an initialized object may not later point to an +object under initialization. As an example, the following code will be rejected: + +``` scala +trait Reporter { def report(msg: String): Unit } +class FileReporter(ctx: Context) extends Reporter { + ctx.typer.reporter = this // ctx now reaches an uninitialized object + val file: File = new File("report.txt") + def report(msg: String) = file.write(msg) +} +``` + +In the code above, suppose `ctx` points to a transitively initialized +object. Now the assignment at line 3 makes `this`, which is not fully +initialized, reachable from `ctx`. This makes field usage dangerous, +as it may indirectly reach uninitialized fields. + +Monotonicity is based on a well-known technique called _heap monotonic +typestate_ to ensure soundness in the presence of aliasing +[1]. Otherwise, either soundness will be compromised or we have to +disallow the usage of already initialized fields. + +Scopability means that an expression may only access existing objects via formal +parameters and `this`. More precisely, given any environment `ρ` (which are the +value bindings for method parameters and `this`) and heap `σ` for evaluating an expression +`e`, if the resulting value reaches an object `o` pre-existent in `σ`, then `o` +is reachable from `ρ` in `σ`. Control effects like coroutines, delimited +control, resumable exceptions may break the property, as they can transport a +value upper in the stack (not in scope) to be reachable from the current scope. +Static fields can also serve as a teleport thus breaks this property. In the +implementation, we need to enforce that teleported values are transitively +initialized. + +With the established principles and design goals, following rules are imposed: + +1. In an assignment `o.x = e`, the expression `e` may only point to transitively initialized objects. + + This is how monotonicity is enforced in the system. Note that in an + initialization `val f: T = e`, the expression `e` may point to an object + under initialization. This requires a distinction between mutation and + initialization in order to enforce different rules. Scala + has different syntax for them, it thus is not an issue. + +2. References to objects under initialization may not be passed as arguments to method calls or constructors. + + Escape of `this` in the constructor is commonly regarded as an + anti-pattern, and it's rarely used in practice. This rule is simple + for the programmer to reason about initialization and it simplifies + implementation. The theory supports safe escape of `this` with the help of + annotations, we delay the extension until there is a strong need. + +3. Local definitions may only refer to transitively initialized objects. + + It means that in a local definition `val x: T = e`, the expression `e` may + only evaluate to transitively initialized objects. The same goes for local + lazy variables and methods. This rule is again motivated for simplicity in + reasoning about initialization: programmers may safely assume that all local + definitions only point to transitively initialized objects. + +## Modularity + +For modularity, we forbid subtle initialization interaction beyond project +boundaries. For example, the following code passes the check when the two +classes are defined in the same project: + +```Scala +class Base { + private val map: mutable.Map[Int, String] = mutable.Map.empty + def enter(k: Int, v: String) = map(k) = v +} +class Child extends Base { + enter(1, "one") + enter(2, "two") +} +``` + +However, when the class `Base` and `Child` are defined in two different +projects, the check will emit a warning for the calls to `enter` in the class +`Child`. This restricts subtle initialization within project boundaries, +and avoids accidental violation of contracts across library versions. + +We impose the following rules to enforce modularity: + +4. A class or trait that may be extended in another project should not + call virtual methods on `this` in its template/mixin evaluation, + directly or indirectly. + +5. The method call `o.m(args)` is forbidden if `o` is not transitively + initialized and the target of `m` is defined in an external project. + +6. The expression `new p.C(args)` is forbidden, if `p` is not transitively + initialized and `C` is defined in an external project. + +Theoretically, we may analyze across project boundaries based on tasty. However, +from our experience with Dotty community projects, most subtle initialization +patterns are restricted in the same project. As the rules only report warnings +instead of errors, we think it is good to first impose more strict rules, The +feedback from the community is welcome. + +## Theory + +The theory is based on type-and-effect systems [2]. We introduce two concepts, +_effects_ and _potentials_: + +``` +π = C.this | Warm(C, π) | π.f | π.m | π.super[D] | Cold | Fun(Π, Φ) | Outer(C, π) +ϕ = π↑ | π.f! | π.m! +``` + +Potentials (π) represent values that are possibly under initialization. + +- `C.this`: current object +- `Warm(C, π)`: an object of type `C` where all its fields are assigned, and the potential for `this` of its enclosing class is `π`. +- `π.f`: the potential of the field `f` in the potential `π` +- `π.m`: the potential of the field `f` in the potential `π` +- `π.super[D]`: essentially the object π, used for virtual method resolution +- `Cold`: an object with unknown initialization status +- `Fun(Π, Φ)`: a function, when called produce effects Φ and return potentials Π. +- `Outer(C, π)`: the potential of `this` for the enclosing class of `C` when `C.this` is ` π`. + +Effects are triggered from potentials: + +- `π↑`: promote the object pointed to by the potential `π` to fully-initialized +- `π.f!`: access field `f` on the potential `π` +- `π.m!`: call the method `m` on the potential `π` + +To ensure that the checking always terminate and for better +performance, we restrict the length of potentials to be finite (by +default 2). If the potential is too long, the checker stops +tracking it by checking that the potential is actually transitively +initialized. + +For an expression `e`, it may be summarized by the pair `(Π, Φ)`, +which means evaluation of `e` may produce the effects Φ and return the +potentials Π. Each field and method is associated with such a pair. +We call such a pair _summary_. The expansion of proxy potentials and effects, +such as `π.f`, `π.m` and `π.m!`, will take advantage of the summaries. +Depending on the potential `π` for `this`, the summaries need to be rebased (`asSeenFrom`) before usage. + +The checking treats the templates of concrete classes as entry points. +It maintains the set of initialized fields as initialization +progresses, and check that only initialized fields are accessed during +the initialization and there is no leaking of values under initialization. +Virtual method calls on `this` is not a problem, +as they can always be resolved statically. + +More details can be found in a forthcoming paper. + +## Back Doors + +Occasionally you may want to suppress warnings reported by the +checker. You can either write `e: @unchecked` to tell the checker to +skip checking for the expression `e`, or you may use the old trick: +mark some fields as lazy. + +## Caveats + +The system cannot handle static fields, nor does it provide safety +guarantee when extending Java or Scala 2 classes. Calling methods of +Java or Scala 2 is always safe. + +## References + +- Fähndrich, M. and Leino, K.R.M., 2003, July. _Heap monotonic typestates_. In International Workshop on Aliasing, Confinement and Ownership in object-oriented programming (IWACO). +- Lucassen, J.M. and Gifford, D.K., 1988, January. _Polymorphic effect systems_. In Proceedings of the 15th ACM SIGPLAN-SIGACT symposium on Principles of programming languages (pp. 47-57). ACM. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 4b976d2957d6..06057115e099 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -107,6 +107,8 @@ sidebar: url: docs/reference/other-new-features/indentation.html - title: Explicit Nulls url: docs/reference/other-new-features/explicit-nulls.html + - title: Safe Initialization + url: docs/reference/other-new-features/safe-initialization.html - title: Other Changed Features subsection: - title: Numeric Literals diff --git a/tests/init/crash/explicitOuter.scala b/tests/init/crash/explicitOuter.scala new file mode 100644 index 000000000000..44b441956420 --- /dev/null +++ b/tests/init/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/crash/explicitOuter.scala.out b/tests/init/crash/explicitOuter.scala.out new file mode 100644 index 000000000000..5c6d987a77aa --- /dev/null +++ b/tests/init/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/crash/fors.scala b/tests/init/crash/fors.scala new file mode 100644 index 000000000000..d8f76473f773 --- /dev/null +++ b/tests/init/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/crash/fors.scala.out b/tests/init/crash/fors.scala.out new file mode 100644 index 000000000000..0fd3e66c70ce --- /dev/null +++ b/tests/init/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/crash/i1990b.scala b/tests/init/crash/i1990b.scala new file mode 100644 index 000000000000..1b1ac932ec2c --- /dev/null +++ b/tests/init/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/crash/i2468.scala b/tests/init/crash/i2468.scala new file mode 100644 index 000000000000..801d92ea6fa6 --- /dev/null +++ b/tests/init/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/crash/i5606.scala b/tests/init/crash/i5606.scala new file mode 100644 index 000000000000..a7b9fc340b60 --- /dev/null +++ b/tests/init/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/crash/i5606.scala.out b/tests/init/crash/i5606.scala.out new file mode 100644 index 000000000000..6e852019c12a --- /dev/null +++ b/tests/init/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/crash/i6109.scala b/tests/init/crash/i6109.scala new file mode 100644 index 000000000000..30093fa05242 --- /dev/null +++ b/tests/init/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/crash/i6109.scala.out b/tests/init/crash/i6109.scala.out new file mode 100644 index 000000000000..e79fda4bfff8 --- /dev/null +++ b/tests/init/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/crash/i6858.scala b/tests/init/crash/i6858.scala new file mode 100644 index 000000000000..d849e91733f1 --- /dev/null +++ b/tests/init/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/crash/i6858.scala.out b/tests/init/crash/i6858.scala.out new file mode 100644 index 000000000000..b0743779c80d --- /dev/null +++ b/tests/init/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/crash/i6914.scala b/tests/init/crash/i6914.scala new file mode 100644 index 000000000000..aa2cd26def6d --- /dev/null +++ b/tests/init/crash/i6914.scala @@ -0,0 +1,29 @@ +trait Expr[T] +trait Liftable[T] + +object test1 { + class ToExpr[T](using Liftable[T]) extends Conversion[T, Expr[T]] { + def apply(x: T): Expr[T] = ??? + } + given toExprFun[T](using Liftable[T]) as 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](using Liftable[T]) as 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/crash/i7821.scala b/tests/init/crash/i7821.scala new file mode 100644 index 000000000000..5903b63219cd --- /dev/null +++ b/tests/init/crash/i7821.scala @@ -0,0 +1,24 @@ +object XObject { + opaque type X = Int + + def anX: X = 5 + + given ops as Object { + def (x: X) + (y: X): X = x + y + } +} + +object MyXObject { + opaque type MyX = XObject.X + + def anX: MyX = XObject.anX + + given ops as Object { + def (x: MyX) + (y: MyX): MyX = x + y // error: warring: Infinite recursive call + } +} + +object Main extends App { + println(XObject.anX + XObject.anX) // prints 10 + println(MyXObject.anX + MyXObject.anX) // infinite loop +} diff --git a/tests/init/crash/opassign.scala b/tests/init/crash/opassign.scala new file mode 100644 index 000000000000..8f6cad903a27 --- /dev/null +++ b/tests/init/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/crash/private-leak.scala b/tests/init/crash/private-leak.scala new file mode 100644 index 000000000000..f4afe02daf4c --- /dev/null +++ b/tests/init/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/crash/rbtree.scala b/tests/init/crash/rbtree.scala new file mode 100644 index 000000000000..6fa03938f5a3 --- /dev/null +++ b/tests/init/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/crash/t0055.scala b/tests/init/crash/t0055.scala new file mode 100644 index 000000000000..ca503dfed81e --- /dev/null +++ b/tests/init/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/crash/t10032.scala b/tests/init/crash/t10032.scala new file mode 100644 index 000000000000..f7e8ef459f3a --- /dev/null +++ b/tests/init/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/crash/t10032.scala.out b/tests/init/crash/t10032.scala.out new file mode 100644 index 000000000000..e75524a628d2 --- /dev/null +++ b/tests/init/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/crash/t2111.scala b/tests/init/crash/t2111.scala new file mode 100644 index 000000000000..98177c4e216f --- /dev/null +++ b/tests/init/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/crash/t2111.scala.out b/tests/init/crash/t2111.scala.out new file mode 100644 index 000000000000..d4ebc338d1c9 --- /dev/null +++ b/tests/init/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/crash/t7120/Base_1.scala b/tests/init/crash/t7120/Base_1.scala new file mode 100644 index 000000000000..be07b4f34f93 --- /dev/null +++ b/tests/init/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/crash/t7120/Derived_2.scala b/tests/init/crash/t7120/Derived_2.scala new file mode 100644 index 000000000000..e0de629f82de --- /dev/null +++ b/tests/init/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/crash/t7120/Run_3.scala b/tests/init/crash/t7120/Run_3.scala new file mode 100644 index 000000000000..95e7f994fffc --- /dev/null +++ b/tests/init/crash/t7120/Run_3.scala @@ -0,0 +1,3 @@ +object Test extends Derived with App { + println( defect( Container( "8" ) ) ) +} diff --git a/tests/init/crash/t8002.scala b/tests/init/crash/t8002.scala new file mode 100644 index 000000000000..f24a213dea52 --- /dev/null +++ b/tests/init/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/crash/t8002.scala.out b/tests/init/crash/t8002.scala.out new file mode 100644 index 000000000000..f9c4d4234c1d --- /dev/null +++ b/tests/init/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/crash/tuples.scala b/tests/init/crash/tuples.scala new file mode 100644 index 000000000000..2ab97cb4aec3 --- /dev/null +++ b/tests/init/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/crash/tuples.scala.out b/tests/init/crash/tuples.scala.out new file mode 100644 index 000000000000..bdd859421fd0 --- /dev/null +++ b/tests/init/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/crash/virtpatmat_alts.scala b/tests/init/crash/virtpatmat_alts.scala new file mode 100644 index 000000000000..5823e06edc68 --- /dev/null +++ b/tests/init/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/AbstractFile.scala b/tests/init/neg/AbstractFile.scala new file mode 100644 index 000000000000..b05b89f32aaf --- /dev/null +++ b/tests/init/neg/AbstractFile.scala @@ -0,0 +1,9 @@ +abstract class AbstractFile { + def name: String + val extension: String = name.substring(4) +} + +class RemoteFile(url: String) extends AbstractFile { + val localFile: String = url.hashCode + ".tmp" // error + def name: String = localFile +} diff --git a/tests/init/neg/alias.scala b/tests/init/neg/alias.scala new file mode 100644 index 000000000000..79367b36d0c5 --- /dev/null +++ b/tests/init/neg/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/by-name-error.scala b/tests/init/neg/by-name-error.scala new file mode 100644 index 000000000000..960bbe9bf7e9 --- /dev/null +++ b/tests/init/neg/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/escape1.scala b/tests/init/neg/escape1.scala new file mode 100644 index 000000000000..588f0933cfc3 --- /dev/null +++ b/tests/init/neg/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/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..77d6a231d79a --- /dev/null +++ b/tests/init/neg/features-linearization.scala @@ -0,0 +1,10 @@ +trait TA { + val x = "world" // error +} + +trait TB { + def x: String + val m = "hello" + x +} +class Foo extends TA with TB +class Bar extends TB with TA 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/final-fields.scala b/tests/init/neg/final-fields.scala new file mode 100644 index 000000000000..174ee9eeb79d --- /dev/null +++ b/tests/init/neg/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 // error + override final val f2 = 2 // error + 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/flow2.scala b/tests/init/neg/flow2.scala new file mode 100644 index 000000000000..a0c5f89601ee --- /dev/null +++ b/tests/init/neg/flow2.scala @@ -0,0 +1,7 @@ +class Foo { + val len = list.size + val list = List(4, 6) // error + + lazy val len2 = list2.size // ok + val list2 = List(4, 6) +} \ No newline at end of file diff --git a/tests/init/neg/function1.scala b/tests/init/neg/function1.scala new file mode 100644 index 000000000000..e01864ae1f47 --- /dev/null +++ b/tests/init/neg/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/function10.scala b/tests/init/neg/function10.scala new file mode 100644 index 000000000000..3d8a72aaa1ea --- /dev/null +++ b/tests/init/neg/function10.scala @@ -0,0 +1,7 @@ +class Base { self => + (0 to 10).foreach { i => // error + println(a) + } + + val a = 10 +} \ No newline at end of file diff --git a/tests/init/neg/function11.scala b/tests/init/neg/function11.scala new file mode 100644 index 000000000000..cb6626291214 --- /dev/null +++ b/tests/init/neg/function11.scala @@ -0,0 +1,33 @@ +final class Capture { + private[this] var m: Boolean = false + + (0 to 10).foreach { i => // error + f() + } + + val a = 10 + + 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/function2.scala b/tests/init/neg/function2.scala new file mode 100644 index 000000000000..0a0f44e2744e --- /dev/null +++ b/tests/init/neg/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/hybrid1.scala b/tests/init/neg/hybrid1.scala new file mode 100644 index 000000000000..94c7a43bfbcd --- /dev/null +++ b/tests/init/neg/hybrid1.scala @@ -0,0 +1,21 @@ +trait A { + def g: Int +} + +class Y { + class X { + class B extends A { + def g = n + } + } + + val x = new X + + class C extends x.B { + g + } + + new C + + val n = 10 // error +} diff --git a/tests/init/neg/hybrid2.scala b/tests/init/neg/hybrid2.scala new file mode 100644 index 000000000000..d5c8b037a324 --- /dev/null +++ b/tests/init/neg/hybrid2.scala @@ -0,0 +1,19 @@ +trait A { + def g: Int +} + +class Y { + + class X { + class B extends A { + def g = n + } + + val b = new B + } + + val x = new X + x.b.g // error + + val n = 10 +} diff --git a/tests/init/neg/hybrid4.scala b/tests/init/neg/hybrid4.scala new file mode 100644 index 000000000000..6243fbf2d5f4 --- /dev/null +++ b/tests/init/neg/hybrid4.scala @@ -0,0 +1,12 @@ +class Foo { + class Inner { + val len = list.size + } + + class Bar { + val inner = new Inner + } + + val bar: Bar = new Bar + val list = List(1, 2, 3) // error +} diff --git a/tests/init/neg/hybrid5.scala b/tests/init/neg/hybrid5.scala new file mode 100644 index 000000000000..54c781b8835d --- /dev/null +++ b/tests/init/neg/hybrid5.scala @@ -0,0 +1,19 @@ +class Foo { + val bar = new Bar + var x: bar.Inner = new bar.Inner + + class Inner { + val len = list.size + } + + class Bar { + class Inner { + val x = g + val len: Int = x.len + } + + def g = new Foo.this.Inner + } + + val list = List(1, 2, 3) // error +} diff --git a/tests/init/neg/hybrid6.scala b/tests/init/neg/hybrid6.scala new file mode 100644 index 000000000000..bbecc16942e5 --- /dev/null +++ b/tests/init/neg/hybrid6.scala @@ -0,0 +1,15 @@ +class Foo { + val bar = new Bar + var x: bar.Inner = new bar.Inner + + class Bar { + class Inner { + val x = g + val len = x.size + } + + def g = list + } + + val list = List(1, 2, 3) // error +} diff --git a/tests/init/neg/inner-case.scala b/tests/init/neg/inner-case.scala new file mode 100644 index 000000000000..fa4ea0250884 --- /dev/null +++ b/tests/init/neg/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-loop.scala b/tests/init/neg/inner-loop.scala new file mode 100644 index 000000000000..a7ff5c153d32 --- /dev/null +++ b/tests/init/neg/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-new.scala b/tests/init/neg/inner-new.scala new file mode 100644 index 000000000000..016179a1d7fc --- /dev/null +++ b/tests/init/neg/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-pat_iuli.scala b/tests/init/neg/inner-pat_iuli.scala new file mode 100644 index 000000000000..35e757cd7cb5 --- /dev/null +++ b/tests/init/neg/inner-pat_iuli.scala @@ -0,0 +1,24 @@ +trait Ops { self: MyCodes => + abstract class Instru + object opcodes { + case class SWITCH(i:Int) extends Instru + case object EmptyInstr extends Instru + } +} + +trait Blox { self: MyCodes => + import opcodes._ + class Basick { + var foo: Instru = null + + def bar = foo match { + case SWITCH(i) => i + case EmptyInstr => 0 + } + } +} + +class MyCodes extends AnyRef with Ops with Blox { + val a = b + val b: Int = 10 // error +} diff --git a/tests/init/neg/inner1.scala b/tests/init/neg/inner1.scala new file mode 100644 index 000000000000..6e8077a500b0 --- /dev/null +++ b/tests/init/neg/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/inner11.scala b/tests/init/neg/inner11.scala new file mode 100644 index 000000000000..7a96a29f8a25 --- /dev/null +++ b/tests/init/neg/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/inner15.scala b/tests/init/neg/inner15.scala new file mode 100644 index 000000000000..59d1daac404f --- /dev/null +++ b/tests/init/neg/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/inner16.scala b/tests/init/neg/inner16.scala new file mode 100644 index 000000000000..61271396a074 --- /dev/null +++ b/tests/init/neg/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/inner17.scala b/tests/init/neg/inner17.scala new file mode 100644 index 000000000000..feb1c2b10229 --- /dev/null +++ b/tests/init/neg/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/inner19.scala b/tests/init/neg/inner19.scala new file mode 100644 index 000000000000..346ca175c802 --- /dev/null +++ b/tests/init/neg/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) // error: leak due to potential length limit + override val n = 50 // error +} \ No newline at end of file diff --git a/tests/init/neg/inner20.scala b/tests/init/neg/inner20.scala new file mode 100644 index 000000000000..1553e09eeeb0 --- /dev/null +++ b/tests/init/neg/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/inner21.scala b/tests/init/neg/inner21.scala new file mode 100644 index 000000000000..c2277163acc3 --- /dev/null +++ b/tests/init/neg/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/inner22.scala b/tests/init/neg/inner22.scala new file mode 100644 index 000000000000..aa5a381f2d49 --- /dev/null +++ b/tests/init/neg/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/inner23.scala b/tests/init/neg/inner23.scala new file mode 100644 index 000000000000..5d373c649ce2 --- /dev/null +++ b/tests/init/neg/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/inner24.scala b/tests/init/neg/inner24.scala new file mode 100644 index 000000000000..f8a60c1bb311 --- /dev/null +++ b/tests/init/neg/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/inner25.scala b/tests/init/neg/inner25.scala new file mode 100644 index 000000000000..8cf99539b849 --- /dev/null +++ b/tests/init/neg/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/inner29.scala b/tests/init/neg/inner29.scala new file mode 100644 index 000000000000..740352db1e4e --- /dev/null +++ b/tests/init/neg/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/inner4.scala b/tests/init/neg/inner4.scala new file mode 100644 index 000000000000..5fc4d2b9e053 --- /dev/null +++ b/tests/init/neg/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/inner5.scala b/tests/init/neg/inner5.scala new file mode 100644 index 000000000000..20f4aa1bdaeb --- /dev/null +++ b/tests/init/neg/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/inner6.scala b/tests/init/neg/inner6.scala new file mode 100644 index 000000000000..42b68fe2d8ea --- /dev/null +++ b/tests/init/neg/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/inner7.scala b/tests/init/neg/inner7.scala new file mode 100644 index 000000000000..bdac38cc31d8 --- /dev/null +++ b/tests/init/neg/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/inner9.scala b/tests/init/neg/inner9.scala new file mode 100644 index 000000000000..db5198ea0138 --- /dev/null +++ b/tests/init/neg/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/lazy.scala b/tests/init/neg/lazy.scala new file mode 100644 index 000000000000..0f3047bc0d1b --- /dev/null +++ b/tests/init/neg/lazy.scala @@ -0,0 +1,11 @@ +class Foo { + val len = list.size + val list = List(4, 6) // error + + lazy val len2 = list2.size // ok + val list2 = List(4, 6) + + lazy val len3 = name.size + val len4 = len3 + 4 + val name = "hello" // error +} diff --git a/tests/init/neg/lazylist1.scala b/tests/init/neg/lazylist1.scala new file mode 100644 index 000000000000..9df3fcfddee7 --- /dev/null +++ b/tests/init/neg/lazylist1.scala @@ -0,0 +1,20 @@ +class LazyList[A] + +object LazyList { + inline implicit def toDeferred[A](l: LazyList[A]): Deferred[A] = + new Deferred(l) // error + + final class Deferred[A](l: => LazyList[A]) { + def #:: [B >: A](elem: => B): LazyList[B] = ??? + } +} + +import LazyList._ + +final class Test { + lazy val a: LazyList[Int] = 5 #:: b + lazy val b: LazyList[Int] = 10 #:: a + + val x: LazyList[Int] = 5 #:: y + val y: LazyList[Int] = 10 #:: x +} \ No newline at end of file diff --git a/tests/init/neg/lazylist2.scala b/tests/init/neg/lazylist2.scala new file mode 100644 index 000000000000..3ef61b6d29e8 --- /dev/null +++ b/tests/init/neg/lazylist2.scala @@ -0,0 +1,43 @@ +import scala.language.implicitConversions + +trait LazyList[+A] { + def isEmpty: Boolean = true + def head: A + def tail: LazyList[A] +} + +object LazyList { + implicit def toHelper[A](l: => LazyList[A]): Helper[A] = new Helper(l) + final class Helper[A](l: => LazyList[A]) { + def #:: [B >: A](elem: => B): LazyList[B] = new LazyList[B] { + override def isEmpty: Boolean = false + override def head: B = elem + override def tail: LazyList[B] = l + } + } +} + +import LazyList._ + +final class Test1 { + lazy val a: LazyList[Int] = 5 #:: b + lazy val b: LazyList[Int] = 10 #:: a + + a.head // ok + b.head // ok + + val x: LazyList[Int] = 5 #:: y // error + val y: LazyList[Int] = 10 #:: x +} + +final class Test2 { + lazy val a: LazyList[Int] = 5 #:: b + lazy val b: LazyList[Int] = 10 #:: 30 #:: c + lazy val c: LazyList[Int] = 20 #:: a +} + +final class Test3 { + val a: LazyList[Int] = n #:: (a: @unchecked) // error + a.head + val n: Int = 20 +} \ No newline at end of file diff --git a/tests/init/neg/override1.scala b/tests/init/neg/override1.scala new file mode 100644 index 000000000000..c9c9fed3ecd8 --- /dev/null +++ b/tests/init/neg/override1.scala @@ -0,0 +1,20 @@ +trait Foo { + val x = 20 + foo(5) + + def foo(n: Int): Int +} + + +abstract class Bar extends Foo { + foo(5) +} + +class Qux(x: Int) extends Bar { + def foo(n: Int) = x + n // ok +} + +class Yun extends Bar { + override val x: Int = 10 // error + def foo(n: Int) = x + n +} diff --git a/tests/init/neg/override10.scala b/tests/init/neg/override10.scala new file mode 100644 index 000000000000..6b8b0e7101db --- /dev/null +++ b/tests/init/neg/override10.scala @@ -0,0 +1,9 @@ +trait Foo { + def f: () => String = () => message + def message: String +} + +class Bar extends Foo { + f() + val message = "hello" // error +} \ No newline at end of file diff --git a/tests/init/neg/override13.scala b/tests/init/neg/override13.scala new file mode 100644 index 000000000000..172fdc2709c8 --- /dev/null +++ b/tests/init/neg/override13.scala @@ -0,0 +1,13 @@ +abstract class A { + val x = f + + def f: Int +} + +class B(val y: Int) extends A { + def f: Int = y +} + +class C extends B(5) { + override val y: Int = 10 // error +} diff --git a/tests/init/neg/override14.scala b/tests/init/neg/override14.scala new file mode 100644 index 000000000000..451a12087103 --- /dev/null +++ b/tests/init/neg/override14.scala @@ -0,0 +1,10 @@ +abstract class A { + val x = f(this) // error + val y = 10 + + def f(a: A): Int +} + +class B extends A { + def f(a: A): Int = a.y +} diff --git a/tests/init/neg/override15.scala b/tests/init/neg/override15.scala new file mode 100644 index 000000000000..0f22430cf6a6 --- /dev/null +++ b/tests/init/neg/override15.scala @@ -0,0 +1,11 @@ +abstract class A { + val g = (n: Int) => n + y + val x: Int = f(g) // error + val y: Int = 10 + + def f(m: Int => Int): Int +} + +class B extends A { + override def f(g: Int => Int): Int = g(20) +} diff --git a/tests/init/neg/override16.scala b/tests/init/neg/override16.scala new file mode 100644 index 000000000000..6e674faf57b1 --- /dev/null +++ b/tests/init/neg/override16.scala @@ -0,0 +1,23 @@ +class A(n: Int) { + val x = n + + def f: Int = x * x +} + +class B(val a: A) { + val b = a.f +} + +class C(override val a: A) extends B(new A(10)) // ok + +class M(val a: A) + +class N(override val a: A) extends M(new A(10)) + +class X(val a: A) { + a.f +} + +class Y extends X(new A(10)) { + override val a: A = ??? // error +} diff --git a/tests/init/neg/override17.scala b/tests/init/neg/override17.scala new file mode 100644 index 000000000000..72bd8db9a246 --- /dev/null +++ b/tests/init/neg/override17.scala @@ -0,0 +1,9 @@ +class A { self : B => + val y = x +} + +trait B { + val x = 10 // error +} + +class C extends A with B diff --git a/tests/init/neg/override18.scala b/tests/init/neg/override18.scala new file mode 100644 index 000000000000..61edaf6d8c8f --- /dev/null +++ b/tests/init/neg/override18.scala @@ -0,0 +1,23 @@ +abstract class A { + def f: Int +} + +class B extends A { + val x = f + + def f: Int = 20 +} + +class C extends A { + val x = f + val y = x // error + + def f: Int = y +} + +class D extends A { + val x = 10 + val y = f + + def f: Int = x // ok +} \ No newline at end of file diff --git a/tests/init/neg/override19.scala b/tests/init/neg/override19.scala new file mode 100644 index 000000000000..8e1f6a832323 --- /dev/null +++ b/tests/init/neg/override19.scala @@ -0,0 +1,10 @@ +abstract class A extends Product { + var n = productArity +} + +case class B(x: Int, y: String) extends A + +case class C(x: Int) extends A { + val y = 10 // error + def productArity: Int = y +} \ No newline at end of file diff --git a/tests/init/neg/override2.scala b/tests/init/neg/override2.scala new file mode 100644 index 000000000000..f9964f60ad29 --- /dev/null +++ b/tests/init/neg/override2.scala @@ -0,0 +1,27 @@ +trait Foo { + val x = "world" + foo(5) + + def bar(x: Int): Int = 20 + + def foo(n: Int): String = x + n +} + +class Bar extends Foo { + val y = "hello" // error + + foo(5) + bar(10) + + override def foo(n: Int): String = { + println("in foo") + y + x + } +} + +class Qux extends Foo { + val y = "hello" + + foo(5) + bar(10) +} diff --git a/tests/init/neg/override20.scala b/tests/init/neg/override20.scala new file mode 100644 index 000000000000..65ece64c6020 --- /dev/null +++ b/tests/init/neg/override20.scala @@ -0,0 +1,9 @@ +class A { self : B => + val y = f +} + +trait B(x: Int) { // error + def f: Int = x +} + +class C extends A with B(20) diff --git a/tests/init/neg/override21.scala b/tests/init/neg/override21.scala new file mode 100644 index 000000000000..332ce702a9fc --- /dev/null +++ b/tests/init/neg/override21.scala @@ -0,0 +1,16 @@ +abstract class Parent { + val x = "name" + lazy val z = bar + def foo = bar + def bar: Int +} + +class Child extends Parent { + this.foo + this.z + val m = this.x + + val y = "hello" // error + + def bar = y.size +} \ No newline at end of file diff --git a/tests/init/neg/override22.scala b/tests/init/neg/override22.scala new file mode 100644 index 000000000000..7de6f03aa837 --- /dev/null +++ b/tests/init/neg/override22.scala @@ -0,0 +1,16 @@ +abstract class Parent { + val x = "name" + lazy val z = bar + def foo = bar + def bar: Int +} + +class Child extends Parent { + val y = "hello" + + this.foo + val m = this.x // error + this.z + + def bar = m.size + 6 +} \ No newline at end of file diff --git a/tests/init/neg/override23.scala b/tests/init/neg/override23.scala new file mode 100644 index 000000000000..3ada767bfafc --- /dev/null +++ b/tests/init/neg/override23.scala @@ -0,0 +1,16 @@ +abstract class Parent(p: String) { + val x = "name" + lazy val z = bar + def foo = bar + def bar: Int +} + +class Child(o: String) extends Parent(o) { + val m = this.x + this.foo + this.z + + val y = "hello" // error + + def bar = y.size +} \ No newline at end of file diff --git a/tests/init/neg/override24.scala b/tests/init/neg/override24.scala new file mode 100644 index 000000000000..9adfac27bb02 --- /dev/null +++ b/tests/init/neg/override24.scala @@ -0,0 +1,21 @@ +import scala.collection.mutable + +abstract class Foo { + private val map: mutable.Map[Int, String] = mutable.Map.empty + + def enter(k: Int, v: String) = map(k) = v + + def name: String + def foo(x: Int) = 5 + name.size +} + +class Bar extends Foo { + enter(1, "one") + enter(2, "two") + + foo(4) + + val name = "bar" // error + + foo(4) +} \ No newline at end of file diff --git a/tests/init/neg/override25.scala b/tests/init/neg/override25.scala new file mode 100644 index 000000000000..9554b4296a9f --- /dev/null +++ b/tests/init/neg/override25.scala @@ -0,0 +1,17 @@ +trait Foo(x: Int) { // error + def name: String = "hello" + + def f: Int = x +} + +trait Bar { this: Foo => + val title = "Mr." + val message = "hello, " + name + + println(title) + + println(f) +} + +class Qux extends Bar with Foo(3) +class Qux2 extends Foo(3) with Bar \ No newline at end of file diff --git a/tests/init/neg/override26.scala b/tests/init/neg/override26.scala new file mode 100644 index 000000000000..37e83898bfcc --- /dev/null +++ b/tests/init/neg/override26.scala @@ -0,0 +1,16 @@ +abstract class Foo { + val name: String = "Foo" + + def title: String +} + +trait Bar { this: Foo => + val message = "hello, " + name + + println(title) +} + +class Qux extends Foo with Bar { + val x = "hello" // error + def title = x +} diff --git a/tests/init/neg/override27.scala b/tests/init/neg/override27.scala new file mode 100644 index 000000000000..4b3a79ac233b --- /dev/null +++ b/tests/init/neg/override27.scala @@ -0,0 +1,17 @@ +abstract class Base { + def f: Int + val a = f // error +} + +class Derived extends Base { + def f = g + + private def g: Int = 30 +} + +class Derived2 extends Base { + val b = 30 // error + def f = g + + def g: Int = b + a +} \ No newline at end of file diff --git a/tests/init/neg/override28.scala b/tests/init/neg/override28.scala new file mode 100644 index 000000000000..8e77f8acb3ba --- /dev/null +++ b/tests/init/neg/override28.scala @@ -0,0 +1,10 @@ +abstract class Base(val x: Int) { + val d: Int + def f: Int = d + val a = x +} + + +class C(x: Int) extends Base(x) { + val d = f // error +} \ No newline at end of file diff --git a/tests/init/neg/override29.scala b/tests/init/neg/override29.scala new file mode 100644 index 000000000000..e0070eb10218 --- /dev/null +++ b/tests/init/neg/override29.scala @@ -0,0 +1,11 @@ +trait A { + var a = 20 // error + def f: Int = a +} + +class B { self : A => + a = 30 + val b = f +} + +class C extends B with A \ No newline at end of file diff --git a/tests/init/neg/override3.scala b/tests/init/neg/override3.scala new file mode 100644 index 000000000000..51759320e1b5 --- /dev/null +++ b/tests/init/neg/override3.scala @@ -0,0 +1,29 @@ +trait Foo { + println("init x") + val x = "world" + val y = foo(5) + + def foo(n: Int): String +} + +class Bar1 extends Foo { + val m = "hello" + + def foo(n: Int) = "world" +} + +class Qux extends Bar1 { + val u = "hello" // error + + override def foo(n: Int) = u + "world" +} + +class Bar2 extends Foo { + val m = "hello" + + final def foo(n: Int) = "world" +} + +class Bar3(m: String) extends Foo { + final def foo(n: Int) = m + "world" +} diff --git a/tests/init/neg/override30.scala b/tests/init/neg/override30.scala new file mode 100644 index 000000000000..adb4c2e00d2b --- /dev/null +++ b/tests/init/neg/override30.scala @@ -0,0 +1,15 @@ +class Foo { + def f: Int = 50 + + def g: Int = f +} + +class Bar extends Foo { + g + f +} + +class Qux extends Bar { + val a = 30 // error + override def f = a +} \ No newline at end of file diff --git a/tests/init/neg/override31.scala b/tests/init/neg/override31.scala new file mode 100644 index 000000000000..d902f4a6dc54 --- /dev/null +++ b/tests/init/neg/override31.scala @@ -0,0 +1,15 @@ +class Foo { + def f: Int = 50 + def init: Int = f +} + +class Bar extends Foo { + private var m = 10 + override def f: Int = m +} + +class Qux extends Bar { + init + override def f = a + private val a = 30 // error +} \ No newline at end of file diff --git a/tests/init/neg/override32.scala b/tests/init/neg/override32.scala new file mode 100644 index 000000000000..09970a9bd392 --- /dev/null +++ b/tests/init/neg/override32.scala @@ -0,0 +1,15 @@ +trait Foo { + val x = "world" + val y = foo(5) + + def foo(n: Int): String +} + +trait Bar { + final val m: String = "hello" // error + + def foo(n: Int) = m +} + +class Qux extends Foo with Bar +class Qux2 extends Bar with Foo \ No newline at end of file diff --git a/tests/init/neg/override33.scala b/tests/init/neg/override33.scala new file mode 100644 index 000000000000..b093dc570f04 --- /dev/null +++ b/tests/init/neg/override33.scala @@ -0,0 +1,18 @@ +abstract class Foo { + def name: String + def title: String + + val message = "hello, " + name + + println(title) +} + +trait Bar { + val name: String = "Foo" // error + + def title: String = name +} + +class Qux extends Foo with Bar { + val x = "hello" +} diff --git a/tests/init/neg/override34.scala b/tests/init/neg/override34.scala new file mode 100644 index 000000000000..6cf3e3cf4b4d --- /dev/null +++ b/tests/init/neg/override34.scala @@ -0,0 +1,13 @@ +abstract class Foo { + def name: String + + val message = "hello, " + name +} + +trait Bar { + val name: String = "Foo" // error +} + +class Qux extends Foo with Bar { + val x = "hello" +} diff --git a/tests/init/neg/override35.scala b/tests/init/neg/override35.scala new file mode 100644 index 000000000000..958ed1ff9d2f --- /dev/null +++ b/tests/init/neg/override35.scala @@ -0,0 +1,15 @@ +class Foo { + def f: Int = 50 + + def g: Int = f +} + +trait Bar { + def g: Int + val a = g +} + +class Qux extends Foo with Bar { + private val x = 30 // error + override def f = x +} \ No newline at end of file diff --git a/tests/init/neg/override36.scala b/tests/init/neg/override36.scala new file mode 100644 index 000000000000..6df0891d5b27 --- /dev/null +++ b/tests/init/neg/override36.scala @@ -0,0 +1,18 @@ +class Foo { + def h: Int = 20 + + final def f: Int = h + + final def init: Int = f +} + +class Bar extends Foo { + private var m = 10 + override def h: Int = m +} + +class Qux extends Bar { + init + override def h = a + private val a = 30 // error +} \ No newline at end of file diff --git a/tests/init/neg/override38.scala b/tests/init/neg/override38.scala new file mode 100644 index 000000000000..dd47b0438980 --- /dev/null +++ b/tests/init/neg/override38.scala @@ -0,0 +1,14 @@ +abstract class A { + def f: Int + + (1 to 10).foreach { i => // error + f + } + + private val a = 10 +} + +class B extends A { + private val a = 30 + def f: Int = a +} \ No newline at end of file diff --git a/tests/init/neg/override39.scala b/tests/init/neg/override39.scala new file mode 100644 index 000000000000..723e71554da2 --- /dev/null +++ b/tests/init/neg/override39.scala @@ -0,0 +1,11 @@ +abstract class Parent extends Product { + val a = productArity + val b = g + + def g: Int = productArity +} + +case class Child(x: Int) extends Parent { + val m = 10 // error + def productArity: Int = m +} \ No newline at end of file diff --git a/tests/init/neg/override4.scala b/tests/init/neg/override4.scala new file mode 100644 index 000000000000..1489e1c2508b --- /dev/null +++ b/tests/init/neg/override4.scala @@ -0,0 +1,20 @@ +import scala.collection.mutable + +class Foo { + private val map: mutable.Map[Int, String] = mutable.Map.empty + + def enter(k: Int, v: String) = map(k) = v +} + +class Bar extends Foo { + enter(1, "one") + enter(2, "two") +} + +class Bar2 extends Bar { + val mymap: mutable.Map[Int, String] = mutable.Map.empty // error + + override def enter(k: Int, v: String) = { + mymap(k) = v + } +} diff --git a/tests/init/neg/override40.scala b/tests/init/neg/override40.scala new file mode 100644 index 000000000000..2f081a9de30b --- /dev/null +++ b/tests/init/neg/override40.scala @@ -0,0 +1,10 @@ +abstract class A { + val a: Int + def f: Int = 10 * a +} + +class B extends A { + this.getClass + f + val a = 20 // error +} \ No newline at end of file diff --git a/tests/init/neg/override41.scala b/tests/init/neg/override41.scala new file mode 100644 index 000000000000..bfd5bc18f3d6 --- /dev/null +++ b/tests/init/neg/override41.scala @@ -0,0 +1,15 @@ +abstract class Parent { + var number = 0 + + def show: Unit +} + +class Child extends Parent { + number = 0 + + println(show) + + def show = println(name) + + val name = "child" // error +} \ No newline at end of file diff --git a/tests/init/neg/override42.scala b/tests/init/neg/override42.scala new file mode 100644 index 000000000000..b4a53b977852 --- /dev/null +++ b/tests/init/neg/override42.scala @@ -0,0 +1,16 @@ +class Foo { + val name = "child" + + println(show) + + def show = println(name) +} + + +class Bar { + println(show) + + def show = println(name) + + val name = "child" // error +} diff --git a/tests/init/neg/override43.scala b/tests/init/neg/override43.scala new file mode 100644 index 000000000000..2d55742c3ecd --- /dev/null +++ b/tests/init/neg/override43.scala @@ -0,0 +1,13 @@ +class Parent { + def foo(): Int = 5 +} + +final class Child extends Parent { + val a = 4 + + def g() = foo() + g() + b + + val b = 10 // error + g() +} \ No newline at end of file diff --git a/tests/init/neg/override44.scala b/tests/init/neg/override44.scala new file mode 100644 index 000000000000..ed7a0ae30b7a --- /dev/null +++ b/tests/init/neg/override44.scala @@ -0,0 +1,13 @@ +class Parent { + def foo(): Int = 5 +} + +class Child extends Parent { + val a = 4 + + def g() = foo() + g() + b + + val b = 10 // error + g() +} \ No newline at end of file diff --git a/tests/init/neg/override45.scala b/tests/init/neg/override45.scala new file mode 100644 index 000000000000..dbf767239e6b --- /dev/null +++ b/tests/init/neg/override45.scala @@ -0,0 +1,12 @@ +class Parent { + lazy val a = 5 * 8 + 9 + lazy val b = f + def f: Int = 20 +} + +class Child extends Parent { + val x = a + val y = b + override def f: Int = z + val z = 30 // error +} \ No newline at end of file diff --git a/tests/init/neg/override46.scala b/tests/init/neg/override46.scala new file mode 100644 index 000000000000..397ec85a6984 --- /dev/null +++ b/tests/init/neg/override46.scala @@ -0,0 +1,9 @@ +trait TA { + val x = "world" // error +} + +trait TB { this: TA => + val m = "hello" + x +} + +class Bar extends TB with TA diff --git a/tests/init/neg/override5.scala b/tests/init/neg/override5.scala new file mode 100644 index 000000000000..8160793c5e35 --- /dev/null +++ b/tests/init/neg/override5.scala @@ -0,0 +1,33 @@ +trait Foo { + def name: String + + val message = "hello, " + name +} + +class Bar extends Foo { + val name = "Jack" // error +} + + +trait Zen { + val name: String + + val message = "hello, " + name +} + +class Tao extends Zen { + val name = "Jack" // error +} + + +trait Base { + val name: String + + val message = "hello, " + name +} + +class Derived(val name: String) extends Base + +class Derived2 extends Derived("hello") { + override val name: String = "ok" // error +} diff --git a/tests/init/neg/override6.scala b/tests/init/neg/override6.scala new file mode 100644 index 000000000000..0c04ea91300d --- /dev/null +++ b/tests/init/neg/override6.scala @@ -0,0 +1,8 @@ +trait Foo { + val name: String + val message = "hello, " + name +} + +class Bar extends Foo { + val name = "Jack" // error +} \ No newline at end of file diff --git a/tests/init/neg/override7.scala b/tests/init/neg/override7.scala new file mode 100644 index 000000000000..0a36f02b2790 --- /dev/null +++ b/tests/init/neg/override7.scala @@ -0,0 +1,21 @@ +trait Foo { + def getName: String + + def getTitle: String + + val message = "hello, " + getTitle + " " + getName +} + +class Bar(val name: String) extends Foo { + val title = "Mr." // error + + def getName = name // ok: name is a Param field + + def getTitle = title +} + +object Test { + def main(args: Array[String]): Unit = { + new Bar("Jack") + } +} diff --git a/tests/init/neg/override8.scala b/tests/init/neg/override8.scala new file mode 100644 index 000000000000..7b4f8bab7b8d --- /dev/null +++ b/tests/init/neg/override8.scala @@ -0,0 +1,35 @@ +trait Foo { + val x = "world" + val y = foo(5) + + def foo(n: Int): String +} + +trait Bar { + val m = "hello" // error + + def foo(n: Int) = m + + def foo(x: String) = "hello, " + x +} + +class Qux extends Foo with Bar +class Qux2 extends Bar with Foo + + +trait Yun { + val m = "hello" + + def foo(n: Int) = m +} + + +class Tao { + private val m = "hello" + + private def msg = "can be overriden" + + def foo(n: Int) = m + msg +} + +class Zen extends Tao with Foo diff --git a/tests/init/neg/override9.scala b/tests/init/neg/override9.scala new file mode 100644 index 000000000000..1b4d32b3e5a3 --- /dev/null +++ b/tests/init/neg/override9.scala @@ -0,0 +1,8 @@ +trait Foo { + def name: String + val message = "hello, " + name // error +} + +class Bar extends Foo { + def name = message +} diff --git a/tests/init/neg/private.scala b/tests/init/neg/private.scala new file mode 100644 index 000000000000..658860ab577c --- /dev/null +++ b/tests/init/neg/private.scala @@ -0,0 +1,15 @@ +class A(a: Int) { + a + 3 + def foo() = a * 2 +} + +class B extends A(3) { + foo() + println(a) + val a = 3 // error +} + +class C extends A(3) { + foo() + val a = 3 +} \ No newline at end of file diff --git a/tests/init/neg/simple1.scala b/tests/init/neg/simple1.scala new file mode 100644 index 000000000000..93fba24008b9 --- /dev/null +++ b/tests/init/neg/simple1.scala @@ -0,0 +1,4 @@ +class Foo { + val len = name.size + val name: String = "Jack" // error +} \ No newline at end of file diff --git a/tests/init/neg/simple2.scala b/tests/init/neg/simple2.scala new file mode 100644 index 000000000000..70a7f8407c6b --- /dev/null +++ b/tests/init/neg/simple2.scala @@ -0,0 +1,20 @@ +class Box(x: Int) { + val y = x + 1 + val z = f(5) + + List(3, 4, 5).map(_ * 2) + + private var a = "hello" // error + + def f(m: Int) = m + a.size +} + +class Box2(x: Int) { + var a = "hello" + val y = x + 1 + val z = f(5) + + List(3, 4, 5).map(_ * 2) + + def f(m: Int) = y + a.size +} diff --git a/tests/init/neg/simple3.scala b/tests/init/neg/simple3.scala new file mode 100644 index 000000000000..70de74b35bd4 --- /dev/null +++ b/tests/init/neg/simple3.scala @@ -0,0 +1,5 @@ +class Foo { + val list = List(4, 6) + val n = len + 5 + val len = list.size // error +} diff --git a/tests/init/neg/simple5.scala b/tests/init/neg/simple5.scala new file mode 100644 index 000000000000..9c9d7d053830 --- /dev/null +++ b/tests/init/neg/simple5.scala @@ -0,0 +1,21 @@ +class Foo { + def b = { + def bar = name.size + bar + } + + b + + val name = "Jack" // error +} + +class Bar { + def b = { + lazy val m = name.size + m + } + + b + + val name = "Jack" // error +} \ No newline at end of file diff --git a/tests/init/neg/soundness1.scala b/tests/init/neg/soundness1.scala new file mode 100644 index 000000000000..8e048a8f72d8 --- /dev/null +++ b/tests/init/neg/soundness1.scala @@ -0,0 +1,7 @@ +class A(b: B) { + val b2 = new B(this) // error +} + +class B(a: A) { + val a2 = new A(this) // error +} diff --git a/tests/init/neg/soundness2.scala b/tests/init/neg/soundness2.scala new file mode 100644 index 000000000000..9d460849c6b0 --- /dev/null +++ b/tests/init/neg/soundness2.scala @@ -0,0 +1,3 @@ +class C(c: C) { + val c2 = new C(this) // error +} diff --git a/tests/init/neg/soundness4.scala b/tests/init/neg/soundness4.scala new file mode 100644 index 000000000000..308856b5e870 --- /dev/null +++ b/tests/init/neg/soundness4.scala @@ -0,0 +1,3 @@ +class Foo { + val a : Foo = this.a.a // error +} \ No newline at end of file diff --git a/tests/init/neg/soundness6.scala b/tests/init/neg/soundness6.scala new file mode 100644 index 000000000000..09d55dba292c --- /dev/null +++ b/tests/init/neg/soundness6.scala @@ -0,0 +1,5 @@ +class C(c: C) { + println(c.n) + val c2 = new C(this) // error + val n = 10 +} diff --git a/tests/init/neg/t3273.check b/tests/init/neg/t3273.check new file mode 100644 index 000000000000..399e186e62cd --- /dev/null +++ b/tests/init/neg/t3273.check @@ -0,0 +1,22 @@ +-- Error: tests/init/neg/t3273.scala:4:42 ------------------------------------------------------------------------------ +4 | val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error + | ^^^^^^^^^^^^^^^ + | Promoting the value to fully-initialized is unsafe. + | Calling trace: + | -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ] + | + | The unsafe promotion may cause the following problem(s): + | + | 1. Access non-initialized field num1. Calling trace: + | -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ] +-- Error: tests/init/neg/t3273.scala:5:61 ------------------------------------------------------------------------------ +5 | val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Promoting the value to fully-initialized is unsafe. + | Calling trace: + | -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ] + | + | The unsafe promotion may cause the following problem(s): + | + | 1. Access non-initialized field num2. Calling trace: + | -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ] diff --git a/tests/init/neg/t3273.scala b/tests/init/neg/t3273.scala new file mode 100644 index 000000000000..141e544bdfeb --- /dev/null +++ b/tests/init/neg/t3273.scala @@ -0,0 +1,12 @@ +import scala.language.implicitConversions + +object Test { + val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error + val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error + + def main(args: Array[String]): Unit = { + val x1 = (num1 take 10).toList + val x2 = (num2 take 10).toList + assert(x1 == x2) + } +} diff --git a/tests/init/neg/trees.scala b/tests/init/neg/trees.scala new file mode 100644 index 000000000000..836f02e9c551 --- /dev/null +++ b/tests/init/neg/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/pos/Properties.scala b/tests/init/pos/Properties.scala new file mode 100644 index 000000000000..df4aee821f9a --- /dev/null +++ b/tests/init/pos/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/pos/assignments.scala b/tests/init/pos/assignments.scala new file mode 100644 index 000000000000..aa20daf02402 --- /dev/null +++ b/tests/init/pos/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 = c.x * 2 + + val cc = c + import cc._ + x = x + 1 + x *= 2 +} diff --git a/tests/init/pos/by-name-inline.scala b/tests/init/pos/by-name-inline.scala new file mode 100644 index 000000000000..ff91d04f47f8 --- /dev/null +++ b/tests/init/pos/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/pos/constant.scala b/tests/init/pos/constant.scala new file mode 100644 index 000000000000..042c264171ba --- /dev/null +++ b/tests/init/pos/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/pos/enum.scala b/tests/init/pos/enum.scala new file mode 100644 index 000000000000..0d31410296df --- /dev/null +++ b/tests/init/pos/enum.scala @@ -0,0 +1,3 @@ +enum MatchCheck { + case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom +} diff --git a/tests/init/pos/exception1.scala b/tests/init/pos/exception1.scala new file mode 100644 index 000000000000..88115e6dd0e9 --- /dev/null +++ b/tests/init/pos/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/pos/features-doublelist.scala b/tests/init/pos/features-doublelist.scala new file mode 100644 index 000000000000..77b4999bf4a6 --- /dev/null +++ b/tests/init/pos/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/pos/features-even.scala b/tests/init/pos/features-even.scala new file mode 100644 index 000000000000..d8882b96f66d --- /dev/null +++ b/tests/init/pos/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/pos/features-scalacheck.scala b/tests/init/pos/features-scalacheck.scala new file mode 100644 index 000000000000..ad7f8eabfc70 --- /dev/null +++ b/tests/init/pos/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/pos/inner13.scala b/tests/init/pos/inner13.scala new file mode 100644 index 000000000000..356083a7cf1a --- /dev/null +++ b/tests/init/pos/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/pos/inner28.scala b/tests/init/pos/inner28.scala new file mode 100644 index 000000000000..0ced985925d8 --- /dev/null +++ b/tests/init/pos/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/pos/llift.scala b/tests/init/pos/llift.scala new file mode 100644 index 000000000000..c0699eac705c --- /dev/null +++ b/tests/init/pos/llift.scala @@ -0,0 +1,156 @@ +class A { + class B { + def outer(): Unit = { + def inner(): Int = 2 + + val fi: Function0[Int] = () => inner() + } + } +} + +object Test { + def foo(x: Int) = { + trait B { + def bar = x + } + class C extends B { + override def bar = super[B].bar + } + new C().bar + } + + def f1(x: Int) = { + class C1 { + def f2 = { + class C2 { + def f3 = { + def f4 = x + f4 + } + } + new C2().f3 + } + } + new C1().f2 + } + + def f1a(x: Int) = { +// class C1 { + def f2 = { + trait T2 { + def f3 = { + def f4 = x + def f5 = f4 + f5 + } + } + class C2 extends T2 + new C2().f3 + } +// } + /*new C1().*/f2 + } + + def f1b(x: Int) = { + class T1 { + def f2 = { + trait T2 { + def f3 = { + def f4 = x + def f5 = f4 + f5 + } + } + class C2 extends T2 + new C2().f3 + } + } + class C1 extends T1 + new C1().f2 + } + + def f1c(x: Int) = { + class T1 { + def f2 = { + trait T2 { + def f3: Int = { + def f4 = x + 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 = x + } + class C1 extends T1 + new C1().f2 + } + + def f1d(x: Int) = { + trait T1 { + def f2 = { + trait T2 { + def f3: Int = { + def f4 = x + 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 = x + } + class C1 extends T1 + new C1().f2 + } + + def f1e(x: Int) = { + trait T1 { + def f2 = { + trait T2 { + def f3: Int = x + } + class C2 extends T2 + new C2().f3 + } + def f6 = x + } + class C1 extends T1 + new C1().f6 + } + + def f1f(x: Int) = { + trait T1 { + trait T2 { + def f3: Int = x + } + class C2 extends T2 { + override def f3 = super.f3 + } + new C2().f3 + def f6 = x + } + class C1 extends T1 + new C1().f6 + } + + def main(args: Array[String]) = { + assert(foo(3) == 3) + assert(f1(4) == 4) + assert(f1a(5) == 5) + assert(f1b(6) == 6) + assert(f1c(7) == 14) + assert(f1d(8) == 16) + assert(f1e(9) == 9) + assert(f1f(10) == 10) + } +} diff --git a/tests/init/pos/nullary.scala b/tests/init/pos/nullary.scala new file mode 100644 index 000000000000..d3b5b67ea53e --- /dev/null +++ b/tests/init/pos/nullary.scala @@ -0,0 +1,20 @@ +abstract class NullaryTest[T, m[s]] { + def nullary: String = "a" + val x = nullary + + def nullary2: T + val x2 = nullary2 + + def nullary3: m[T] + val x3 = nullary3 +} + +class Concrete extends NullaryTest[Int, List] { + def nullary2 = 1 + def nullary3 = List(1,2,3) +} + +object test { + (new Concrete).nullary2 + (new Concrete).nullary3 +} diff --git a/tests/init/pos/recursive-new.scala b/tests/init/pos/recursive-new.scala new file mode 100644 index 000000000000..a57e1f30cefb --- /dev/null +++ b/tests/init/pos/recursive-new.scala @@ -0,0 +1,9 @@ +class A { + val a = new A // ok + lazy val b = new A // ok + val f = () => new A // ok +} + +object Test { + def main(args: Array[String]): Unit = new A +} diff --git a/tests/init/pos/second-ctor.scala b/tests/init/pos/second-ctor.scala new file mode 100644 index 000000000000..ac66c5caf800 --- /dev/null +++ b/tests/init/pos/second-ctor.scala @@ -0,0 +1,10 @@ +class Base { + private var topId: Int = 10 + + def this(x: Int) = { + this() + println(topId) + } +} + +class Child extends Base(5) \ No newline at end of file diff --git a/tests/init/pos/soundness3.scala b/tests/init/pos/soundness3.scala new file mode 100644 index 000000000000..16ba566c702b --- /dev/null +++ b/tests/init/pos/soundness3.scala @@ -0,0 +1,4 @@ +class Foo { + println(f) + def f: Foo = f.f +} diff --git a/tests/init/pos/soundness5.scala b/tests/init/pos/soundness5.scala new file mode 100644 index 000000000000..64fba16ada20 --- /dev/null +++ b/tests/init/pos/soundness5.scala @@ -0,0 +1,5 @@ +class Foo { + var a: Int = h() + def h(): Int = g() + def g(): Int = h() +} \ No newline at end of file diff --git a/tests/init/pos/soundness7.scala b/tests/init/pos/soundness7.scala new file mode 100644 index 000000000000..01c6d9c48e13 --- /dev/null +++ b/tests/init/pos/soundness7.scala @@ -0,0 +1,4 @@ +class A { + class B extends A + new B +} \ No newline at end of file diff --git a/tests/init/pos/super1.scala b/tests/init/pos/super1.scala new file mode 100644 index 000000000000..3c472194f281 --- /dev/null +++ b/tests/init/pos/super1.scala @@ -0,0 +1,16 @@ +import scala.collection.mutable + +class Foo { + private val map: mutable.Map[Int, String] = mutable.Map.empty + + def enter(k: Int, v: String) = map(k) = v +} + +class Bar extends Foo { + override def enter(k: Int, v: String) = ??? + def enterSuper(k: Int, v: String) = super.enter(k, v) + + enter(1, "one") + enterSuper(1, "one") + super.enter(2, "two") +} diff --git a/tests/init/pos/synchronized.scala b/tests/init/pos/synchronized.scala new file mode 100644 index 000000000000..f719e175323b --- /dev/null +++ b/tests/init/pos/synchronized.scala @@ -0,0 +1,9 @@ +class A { + this.synchronized { 5 } + "hello" eq this + "hello" ne this + this.getClass + + lazy val a: Int = List(3, 5).size + a +} \ No newline at end of file