From dc2766443b1fb93e5d87e47c41bd4291e501a564 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 May 2018 06:02:52 +0200 Subject: [PATCH 01/83] Add tests and implementation --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../src/dotty/tools/dotc/ast/TreeInfo.scala | 6 +- .../dotty/tools/dotc/config/Printers.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 6 + .../src/dotty/tools/dotc/core/Flags.scala | 1 + .../src/dotty/tools/dotc/core/StdNames.scala | 5 + .../tools/dotc/transform/FirstTransform.scala | 4 +- .../tools/dotc/transform/init/Analyzer.scala | 312 +++++++++ .../tools/dotc/transform/init/Checker.scala | 281 +++++++++ .../tools/dotc/transform/init/Effects.scala | 93 +++ .../tools/dotc/transform/init/Heap.scala | 322 ++++++++++ .../tools/dotc/transform/init/Indexer.scala | 138 ++++ .../dotc/transform/init/ShowSetting.scala | 18 + .../tools/dotc/transform/init/Values.scala | 594 ++++++++++++++++++ .../tools/dotc/transform/init/package.scala | 61 ++ .../dotty/tools/dotc/CompilationTests.scala | 6 + library/src/dotty/DottyPredef.scala | 3 + library/src/scala/annotation/filled.scala | 18 + library/src/scala/annotation/init.scala | 5 + library/src/scala/annotation/partial.scala | 13 + tests/neg-custom-args/isInstanceOf/1828.scala | 4 +- .../neg-custom-args/isInstanceOf/3324g.scala | 2 +- .../safe-init/Properties.scala | 102 +++ tests/neg-custom-args/safe-init/cycle.scala | 21 + .../safe-init/double-list.scala | 13 + .../safe-init/double-list2.scala | 17 + tests/neg-custom-args/safe-init/early1.scala | 13 + tests/neg-custom-args/safe-init/early2.scala | 13 + tests/neg-custom-args/safe-init/escape1.scala | 8 + tests/neg-custom-args/safe-init/escape2.scala | 10 + tests/neg-custom-args/safe-init/example.scala | 46 ++ tests/neg-custom-args/safe-init/flags.scala | 19 + tests/neg-custom-args/safe-init/flow2.scala | 7 + .../neg-custom-args/safe-init/function.scala | 12 + .../neg-custom-args/safe-init/function2.scala | 12 + .../neg-custom-args/safe-init/function3.scala | 29 + .../neg-custom-args/safe-init/function4.scala | 16 + .../neg-custom-args/safe-init/function5.scala | 19 + .../neg-custom-args/safe-init/function6.scala | 33 + .../neg-custom-args/safe-init/function7.scala | 15 + .../neg-custom-args/safe-init/function8.scala | 17 + .../neg-custom-args/safe-init/function9.scala | 20 + tests/neg-custom-args/safe-init/if.scala | 44 ++ .../safe-init/inner0.scala.bak | 9 + tests/neg-custom-args/safe-init/inner1.scala | 26 + tests/neg-custom-args/safe-init/inner10.scala | 11 + tests/neg-custom-args/safe-init/inner11.scala | 22 + tests/neg-custom-args/safe-init/inner2.scala | 23 + tests/neg-custom-args/safe-init/inner3.scala | 21 + tests/neg-custom-args/safe-init/inner4.scala | 11 + tests/neg-custom-args/safe-init/inner5.scala | 8 + tests/neg-custom-args/safe-init/inner6.scala | 21 + tests/neg-custom-args/safe-init/inner7.scala | 15 + tests/neg-custom-args/safe-init/inner8.scala | 27 + tests/neg-custom-args/safe-init/inner9.scala | 20 + tests/neg-custom-args/safe-init/nested1.scala | 26 + .../neg-custom-args/safe-init/override1.scala | 17 + .../neg-custom-args/safe-init/override2.scala | 20 + .../neg-custom-args/safe-init/override3.scala | 34 + .../neg-custom-args/safe-init/override4.scala | 38 ++ .../neg-custom-args/safe-init/override5.scala | 36 ++ .../neg-custom-args/safe-init/override6.scala | 8 + .../neg-custom-args/safe-init/override7.scala | 41 ++ .../neg-custom-args/safe-init/override8.scala | 48 ++ .../neg-custom-args/safe-init/override9.scala | 9 + tests/neg-custom-args/safe-init/parent1.scala | 16 + tests/neg-custom-args/safe-init/parent2.scala | 16 + tests/neg-custom-args/safe-init/parent3.scala | 16 + tests/neg-custom-args/safe-init/parent4.scala | 12 + tests/neg-custom-args/safe-init/parent5.scala | 21 + tests/neg-custom-args/safe-init/parent6.scala | 21 + tests/neg-custom-args/safe-init/parent7.scala | 12 + tests/neg-custom-args/safe-init/parent8.scala | 11 + .../safe-init/partial-select1.scala | 19 + .../safe-init/partial-select2.scala | 15 + .../safe-init/partial-select3.scala | 16 + tests/neg-custom-args/safe-init/periods.scala | 48 ++ .../neg-custom-args/safe-init/simple-1a.scala | 4 + .../neg-custom-args/safe-init/simple-1b.scala | 21 + .../neg-custom-args/safe-init/simple-1c.scala | 6 + .../neg-custom-args/safe-init/simple-1d.scala | 11 + .../neg-custom-args/safe-init/simple-1e.scala | 13 + .../neg-custom-args/safe-init/simple-1f.scala | 22 + .../neg-custom-args/safe-init/simple-1g.scala | 3 + .../neg-custom-args/safe-init/simple-1h.scala | 8 + .../neg-custom-args/safe-init/simple-1i.scala | 11 + .../safe-init/tailrec.scala.bak | 7 + .../safe-init/unchecked1.scala | 36 ++ tests/patmat/i3645e.check | 2 +- tests/patmat/i3645e.scala | 3 + tests/patmat/i3645f.check | 2 +- tests/patmat/i3645f.scala | 3 + tests/patmat/i3645g.check | 2 +- tests/patmat/i3645g.scala | 3 + tests/patmat/i3938.scala | 2 +- 95 files changed, 3242 insertions(+), 11 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Checker.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Effects.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Heap.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Indexer.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Values.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/init/package.scala create mode 100644 library/src/scala/annotation/filled.scala create mode 100644 library/src/scala/annotation/init.scala create mode 100644 library/src/scala/annotation/partial.scala create mode 100644 tests/neg-custom-args/safe-init/Properties.scala create mode 100644 tests/neg-custom-args/safe-init/cycle.scala create mode 100644 tests/neg-custom-args/safe-init/double-list.scala create mode 100644 tests/neg-custom-args/safe-init/double-list2.scala create mode 100644 tests/neg-custom-args/safe-init/early1.scala create mode 100644 tests/neg-custom-args/safe-init/early2.scala create mode 100644 tests/neg-custom-args/safe-init/escape1.scala create mode 100644 tests/neg-custom-args/safe-init/escape2.scala create mode 100644 tests/neg-custom-args/safe-init/example.scala create mode 100644 tests/neg-custom-args/safe-init/flags.scala create mode 100644 tests/neg-custom-args/safe-init/flow2.scala create mode 100644 tests/neg-custom-args/safe-init/function.scala create mode 100644 tests/neg-custom-args/safe-init/function2.scala create mode 100644 tests/neg-custom-args/safe-init/function3.scala create mode 100644 tests/neg-custom-args/safe-init/function4.scala create mode 100644 tests/neg-custom-args/safe-init/function5.scala create mode 100644 tests/neg-custom-args/safe-init/function6.scala create mode 100644 tests/neg-custom-args/safe-init/function7.scala create mode 100644 tests/neg-custom-args/safe-init/function8.scala create mode 100644 tests/neg-custom-args/safe-init/function9.scala create mode 100644 tests/neg-custom-args/safe-init/if.scala create mode 100644 tests/neg-custom-args/safe-init/inner0.scala.bak create mode 100644 tests/neg-custom-args/safe-init/inner1.scala create mode 100644 tests/neg-custom-args/safe-init/inner10.scala create mode 100644 tests/neg-custom-args/safe-init/inner11.scala create mode 100644 tests/neg-custom-args/safe-init/inner2.scala create mode 100644 tests/neg-custom-args/safe-init/inner3.scala create mode 100644 tests/neg-custom-args/safe-init/inner4.scala create mode 100644 tests/neg-custom-args/safe-init/inner5.scala create mode 100644 tests/neg-custom-args/safe-init/inner6.scala create mode 100644 tests/neg-custom-args/safe-init/inner7.scala create mode 100644 tests/neg-custom-args/safe-init/inner8.scala create mode 100644 tests/neg-custom-args/safe-init/inner9.scala create mode 100644 tests/neg-custom-args/safe-init/nested1.scala create mode 100644 tests/neg-custom-args/safe-init/override1.scala create mode 100644 tests/neg-custom-args/safe-init/override2.scala create mode 100644 tests/neg-custom-args/safe-init/override3.scala create mode 100644 tests/neg-custom-args/safe-init/override4.scala create mode 100644 tests/neg-custom-args/safe-init/override5.scala create mode 100644 tests/neg-custom-args/safe-init/override6.scala create mode 100644 tests/neg-custom-args/safe-init/override7.scala create mode 100644 tests/neg-custom-args/safe-init/override8.scala create mode 100644 tests/neg-custom-args/safe-init/override9.scala create mode 100644 tests/neg-custom-args/safe-init/parent1.scala create mode 100644 tests/neg-custom-args/safe-init/parent2.scala create mode 100644 tests/neg-custom-args/safe-init/parent3.scala create mode 100644 tests/neg-custom-args/safe-init/parent4.scala create mode 100644 tests/neg-custom-args/safe-init/parent5.scala create mode 100644 tests/neg-custom-args/safe-init/parent6.scala create mode 100644 tests/neg-custom-args/safe-init/parent7.scala create mode 100644 tests/neg-custom-args/safe-init/parent8.scala create mode 100644 tests/neg-custom-args/safe-init/partial-select1.scala create mode 100644 tests/neg-custom-args/safe-init/partial-select2.scala create mode 100644 tests/neg-custom-args/safe-init/partial-select3.scala create mode 100644 tests/neg-custom-args/safe-init/periods.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1a.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1b.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1c.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1d.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1e.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1f.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1g.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1h.scala create mode 100644 tests/neg-custom-args/safe-init/simple-1i.scala create mode 100644 tests/neg-custom-args/safe-init/tailrec.scala.bak create mode 100644 tests/neg-custom-args/safe-init/unchecked1.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3bf3ea57d918..ec5be939881a 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -49,6 +49,7 @@ class Compiler { /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = + List(new init.Checker) :: // Check safe initialization of class fields List(new FirstTransform, // Some transformations to put trees into a canonical form 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 diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 30eb2b16ba5e..4a83463d7815 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -559,14 +559,16 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * * Note: targ and vargss may be empty */ - def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = { - @tailrec + def decomposeCall(tree: Tree)(implicit ctx: Context): (Tree, List[Tree], List[List[Tree]]) = { def loop(tree: Tree, targss: List[Tree], argss: List[List[Tree]]): (Tree, List[Tree], List[List[Tree]]) = tree match { case Apply(fn, args) => loop(fn, targss, args :: argss) case TypeApply(fn, targs) => loop(fn, targs ::: targss, argss) + case Block(stats, expr) => + val (fn, targss2, argss2) = loop(expr, targss, argss) + (Block(stats, fn), targss2, argss2) case _ => (tree, targss, argss) } diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8a7cd091ccb0..ddb2f171e13b 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -21,6 +21,7 @@ object Printers { val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter val hk: Printer = noPrinter + val init: Printer = noPrinter val implicits: Printer = noPrinter val implicitsDetailed: Printer = noPrinter val inlining: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 16009425a725..6af460b43dfc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -806,6 +806,12 @@ class Definitions { def SetterMetaAnnot(implicit ctx: Context): ClassSymbol = SetterMetaAnnotType.symbol.asClass lazy val ShowAsInfixAnotType: TypeRef = ctx.requiredClassRef("scala.annotation.showAsInfix") def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass + lazy val PartialAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.partial") + def PartialAnnot(implicit ctx: Context): ClassSymbol = PartialAnnotType.symbol.asClass + lazy val FilledAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.filled") + def FilledAnnot(implicit ctx: Context): ClassSymbol = FilledAnnotType.symbol.asClass + lazy val InitAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.init") + def InitAnnot(implicit ctx: Context): ClassSymbol = InitAnnotType.symbol.asClass // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 45aeeef52ec1..20209cda691c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -3,6 +3,7 @@ package core import language.implicitConversions +@unchecked object Flags { /** A FlagSet represents a set of flags. Flags are encoded as follows: diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 98e5ed22a024..52250fdd2b18 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -358,6 +358,7 @@ object StdNames { val UNIT : N = "UNIT" val add_ : N = "add" val annotation: N = "annotation" + val andThen: N = "andThen" val anyHash: N = "anyHash" val anyValClass: N = "anyValClass" val append: N = "append" @@ -392,6 +393,7 @@ object StdNames { val ClassManifestFactory: N = "ClassManifestFactory" val classOf: N = "classOf" val clone_ : N = "clone" + val compose : N = "compose" val conforms_ : N = "$conforms" val copy: N = "copy" val currentMirror: N = "currentMirror" @@ -452,6 +454,7 @@ object StdNames { val lang: N = "lang" val length: N = "length" val lengthCompare: N = "lengthCompare" + val lift: N = "lift" val `macro` : N = "macro" val macroThis : N = "_this" val macroContext : N = "c" @@ -480,6 +483,7 @@ object StdNames { val null_ : N = "null" val ofDim: N = "ofDim" val origin: N = "origin" + val orElse: N = "orElse" val prefix : N = "prefix" val productArity: N = "productArity" val productElement: N = "productElement" @@ -496,6 +500,7 @@ object StdNames { val runtime: N = "runtime" val runtimeClass: N = "runtimeClass" val runtimeMirror: N = "runtimeMirror" + val runWith: N = "runWith" val s: N = "s" val sameElements: N = "sameElements" val scala_ : N = "scala" diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index de660a9df6ec..80c131ff85dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -52,8 +52,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => assert( - qual.tpe.derivesFrom(tree.symbol.owner) || - tree.symbol.is(JavaStatic) && qual.tpe.derivesFrom(tree.symbol.enclosingClass), + qual.tpe.widenDealias.derivesFrom(tree.symbol.owner) || + tree.symbol.is(JavaStatic) && qual.tpe.widenDealias.derivesFrom(tree.symbol.enclosingClass), i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe} in $tree") case _: TypeTree => case _: Import | _: NamedArg | _: TypTree => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala new file mode 100644 index 000000000000..83825e1596c3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -0,0 +1,312 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import MegaPhase._ +import Contexts.Context +import StdNames._ +import Names._ +import Phases._ +import ast._ +import Trees._ +import Flags._ +import SymUtils._ +import Symbols._ +import Denotations._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import Constants.Constant +import collection.mutable + + +class Analyzer extends Indexer { analyzer => + import tpd._ + + var depth: Int = 0 + + def trace(msg: => String, env: Env)(body: => Res)(implicit ctx: Context) = { + indentedDebug(s"==> ${pad(msg)}?") + indentedDebug("heap = " + env.heap.show) + indentedDebug(env.show(ShowSetting(env.heap))) + depth += 1 + val res = body + depth -= 1 + indentedDebug(s"<== ${pad(msg)} = ${pad(res.show(ShowSetting(env.heap)))}") + res + } + + def pad(s: String, padFirst: Boolean = false) = ShowSetting.pad(s, depth, padFirst) + + def indentedDebug(msg: => String) = debug(ShowSetting.pad(msg, depth, padFirst = true)) + + def checkApply(tree: tpd.Tree, fun: Tree, argss: List[List[Tree]], env: Env)(implicit ctx: Context): Res = { + val funSym = fun.symbol + val funRes = apply(fun, env) + + val args = argss.flatten + val values = args.map { arg => + val res = apply(arg, env) + funRes ++= res.effects + res.value + } + + indentedDebug(s">>> calling $funSym") + funRes.value(values, args.map(_.pos), tree.pos, env.heap) ++ funRes.effects + } + + def checkSelect(tree: Select, env: Env)(implicit ctx: Context): Res = { + val prefixRes = apply(tree.qualifier, env) + val res = prefixRes.value.select(tree.symbol, env.heap, tree.pos) + res.effects = prefixRes.effects ++ res.effects + res + } + + private def enclosedIn(curSym: Symbol, inSym: Symbol)(implicit ctx: Context): Boolean = + curSym.exists && ((curSym `eq` inSym) || (enclosedIn(curSym.owner, inSym))) + + def checkRef(tp: Type, env: Env, pos: Position)(implicit ctx: Context): Res = trace("checking " + tp.show, env)(tp match { + case tp : TermRef if tp.symbol.is(Module) && enclosedIn(ctx.owner, tp.symbol.moduleClass) => + // self reference by name: object O { ... O.xxx } + checkRef(ThisType.raw(tp.symbol.moduleClass.typeRef), env, pos) + case tp @ TermRef(NoPrefix, _) => + env.select(tp.symbol, pos) + case tp @ TermRef(prefix, _) => + val res = checkRef(prefix, env, pos) + res.value.select(tp.symbol, env.heap, pos) + case tp @ ThisType(tref) => + val cls = tref.symbol + if (cls.is(Package)) Res() // Dotty represents package path by ThisType + else if (env.contains(cls)) Res(value = env(cls)) + else { + // ThisType used outside of class scope, can happen for objects + // see tests/pos/t2712-7.scala + assert(cls.is(Flags.Module) && !enclosedIn(ctx.owner, cls)) + Res() + } + case tp @ SuperType(thistpe, supertpe) => + // TODO : handle `supertpe` + checkRef(thistpe, env, pos) + }) + + def checkClosure(sym: Symbol, tree: Tree, env: Env)(implicit ctx: Context): Res = { + if (env.contains(sym)) Res(value = env(sym)) else Res() + } + + def checkIf(tree: If, env: Env)(implicit ctx: Context): Res = { + val If(cond, thenp, elsep) = tree + + val condRes: Res = apply(cond, env) + + def makeFun(body: Tree) = new FunctionValue { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val envCurrent = heap(env.id).asEnv + analyzer.apply(body, envCurrent) + } + } + + val thenFun = makeFun(thenp) + val elseFun = makeFun(elsep) + + val res = thenFun.join(elseFun).apply(Nil, Nil, NoPosition, env.heap) + res ++= condRes.effects + res + } + + def checkValDef(vdef: ValDef, env: Env)(implicit ctx: Context): Res = { + val rhsRes = apply(vdef.rhs, env) + val sym = vdef.symbol + + // take `_` as uninitialized, otherwise it's initialized + if (!tpd.isWildcardArg(vdef.rhs)) sym.termRef match { + case tp @ TermRef(NoPrefix, _) => + env.assign(tp.symbol, rhsRes.value, vdef.rhs.pos) + case tp @ TermRef(prefix, _) => + val prefixRes = checkRef(prefix, env, vdef.rhs.pos) + assert(!prefixRes.hasErrors) + prefixRes.value.assign(sym, rhsRes.value, env.heap, vdef.pos) + } + + Res(effects = rhsRes.effects) + } + + def checkStats(stats: List[Tree], env: Env)(implicit ctx: Context): Res = + stats.foldLeft(Res()) { (acc, stat) => + indentedDebug(s"acc = ${pad(acc.show(ShowSetting(env.heap)))}") + val res1 = apply(stat, env) + acc.copy(effects = acc.effects ++ res1.effects) + } + + def checkBlock(tree: Block, env: Env)(implicit ctx: Context): Res = { + val newEnv = env.fresh() + indexStats(tree.stats, newEnv) + + val res1 = checkStats(tree.stats, newEnv) + val res2 = apply(tree.expr, newEnv) + + res2.copy(effects = res1.effects ++ res2.effects) + } + + protected var _methChecking: Set[Symbol] = Set() + def isChecking(sym: Symbol) = _methChecking.contains(sym) + def checking[T](sym: Symbol)(fn: => T) = { + _methChecking += sym + val res = fn + _methChecking -= sym + res + } + + def checkAssign(lhs: Tree, rhs: Tree, env: Env)(implicit ctx: Context): Res = { + val rhsRes = apply(rhs, env) + if (rhsRes.hasErrors) return rhsRes + + lhs match { + case ident @ Ident(_) => + ident.tpe match { + case tp @ TermRef(NoPrefix, _) => + env.assign(tp.symbol, rhsRes.value, rhs.pos) + case tp @ TermRef(prefix, _) => + val prefixRes = checkRef(prefix, env, rhs.pos) + if (prefixRes.hasErrors) prefixRes + else prefixRes.value.assign(tp.symbol, rhsRes.value, env.heap, rhs.pos) + } + case sel @ Select(qual, _) => + val prefixRes = apply(qual, env) + prefixRes.value.assign(sel.symbol, rhsRes.value, env.heap, rhs.pos) + } + } + + /** Check a parent call */ + def checkInit(tp: Type, init: Symbol, argss: List[List[Tree]], env: Env, obj: ObjectValue, pos: Position)(implicit ctx: Context): Res = { + if (!init.exists) return Res() + + val cls = init.owner.asClass + val args = argss.flatten + + // setup constructor params + var effs = Vector.empty[Effect] + val argValues = args.map { arg => + val res = apply(arg, env) + effs = effs ++ res.effects + res.value + } + + if (effs.nonEmpty) return Res(effs) + + def toPrefix(tp: Type): Type = tp match { + case AppliedType(tycon, _) => toPrefix(tycon.dealias) + case tp: TypeRef => tp.prefix + } + + val prefix = toPrefix(tp) + if (prefix == NoPrefix) env.init(init, argValues, args.map(_.pos), pos, obj, this) + else { + val prefixRes = checkRef(prefix, env, pos) + if (prefixRes.hasErrors) return prefixRes + prefixRes.value.init(init, argValues, args.map(_.pos), pos, obj, env.heap, this) + } + } + + def checkParents(cls: ClassSymbol, parents: List[Tree], env: Env, obj: ObjectValue)(implicit ctx: Context): Res = { + if (cls.is(Trait)) return Res() + + def blockInit(stats: List[Tree], parent: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]]): Res = { + val newEnv = env.fresh() + indexStats(stats, newEnv) + val res = checkStats(stats, newEnv) + res ++ checkInit(parent.tpe, init, argss, newEnv, obj, parent.pos).effects + } + + + // first call super class, see spec 5.1 about "Template Evaluation". + val res = parents.head match { + case parent @ NewEx(tref, init, argss) => + checkInit(parent.tpe, init, argss, env, obj, parent.pos) + case Block(stats, parent @ NewEx(tref, init, argss)) => + blockInit(stats, parent, tref, init, argss) + case Apply(Block(stats, parent @ NewEx(tref, init, argss)), args) => + blockInit(stats, Apply(parent, args), tref, init, argss :+ args) + } + + if (res.hasErrors) return res + + val superCls = parents.head.tpe.classSymbol + val remains = cls.baseClasses.tail.takeWhile(_ `ne` superCls).reverse + + // handle remaning traits + remains.foldLeft(res) { (acc, traitCls) => + val parentOpt = parents.find(_.tpe.classSymbol `eq` traitCls) + parentOpt match { + case Some(parent @ NewEx(tref, init, argss)) => + checkInit(parent.tpe, init, argss, env, obj, parent.pos).join(acc) + case _ => + val tp = obj.tp.baseType(traitCls) + checkInit(tp, traitCls.primaryConstructor, Nil, env, obj, cls.pos).join(acc) + } + } + } + + def checkNew(tree: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]], env: Env)(implicit ctx: Context): Res = { + val obj = new ObjectValue(tree.tpe, open = false) + val res = checkInit(obj.tp, init, argss, env, obj, tree.pos) + if (obj.slices.isEmpty) { + res.copy(value = FullValue) + } + else { + if (res.hasErrors) res.effects = Vector(Instantiate(tree.tpe.classSymbol, res.effects, tree.pos)) + res.copy(value = obj) + } + } + + object NewEx { + def extract(tp: Type)(implicit ctx: Context): TypeRef = tp.dealias match { + case tref: TypeRef => tref + case AppliedType(tref: TypeRef, targs) => tref + } + + def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[(TypeRef, Symbol, List[List[tpd.Tree]])] = { + val (fn, targs, vargss) = tpd.decomposeCall(tree) + if (!fn.symbol.isConstructor || !tree.isInstanceOf[tpd.Apply]) None + else { + val Select(New(tpt), _) = fn + Some((extract(tpt.tpe), fn.symbol, vargss)) + } + } + } + + def apply(tree: Tree, env: Env)(implicit ctx: Context): Res = trace("checking " + tree.show, env)(tree match { + case vdef : ValDef if !vdef.symbol.is(Lazy) && !vdef.rhs.isEmpty => + checkValDef(vdef, env) + case _: DefTree => // ignore, definitions, already indexed + Res() + case Closure(_, meth, _) => + checkClosure(meth.symbol, tree, env) + case tree: Ident if tree.symbol.isTerm => + checkRef(tree.tpe, env, tree.pos) + case tree: This => + checkRef(tree.tpe, env, tree.pos) + case tree: Super => + checkRef(tree.tpe, env, tree.pos) + case tree: Select if tree.symbol.isTerm => + checkSelect(tree, env) + case tree: If => + checkIf(tree, env) + case tree @ NewEx(tref, init, argss) => // must before Apply + checkNew(tree, tref, init, argss, env) + case tree: Apply => + val (fn, targs, vargss) = decomposeCall(tree) + checkApply(tree, fn, vargss, env) + case tree @ Assign(lhs, rhs) => + checkAssign(lhs, rhs, env) + case tree: Block => + checkBlock(tree, env) + case Typed(expr, tpt) if !tpt.tpe.hasAnnotation(defn.UncheckedAnnot) => + apply(expr, env) + case _ => + Res() + }) +} \ No newline at end of file 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..04325d0a404a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -0,0 +1,281 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import MegaPhase._ +import Contexts.Context +import StdNames._ +import Names._ +import Phases._ +import ast._ +import Trees._ +import Flags._ +import SymUtils._ +import Symbols._ +import Denotations._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import Constants.Constant +import collection.mutable + +object Checker { + val name = "initChecker" +} + +/** This transform checks initialization is safe based on data-flow analysis + * + * - Partial + * - Filled + * - Full + * + * 1. A _full_ object is fully initialized. + * 2. All fields of a _filled_ object are assigned, but the fields may refer to non-full objects. + * 3. A _partial_ object may have unassigned fields. + * + * TODO: + * - check default arguments of init methods + * - selection on ParamAccessors of partial value is fine if the param is not partial + * - handle tailrec calls during initialization (which captures `this`) + */ +class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => + import tpd._ + + override def phaseName: String = Checker.name + + override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = { + val cls = ctx.owner.asClass + val self = cls.thisType + + // ignore init checking if `@unchecked` + if (cls.hasAnnotation(defn.UncheckedAnnot)) return tree + + def lateInitMsg(sym: Symbol) = + s"""|Initialization too late: $sym may be used during parent initialization. + |Consider make it a class parameter.""" + .stripMargin + + for (decl <- cls.info.decls.toList if decl.is(AnyFlags, butNot = Method | Deferred)) { + if (!decl.is(ParamAccessor | Override) && decl.isOverride) + ctx.warning(lateInitMsg(decl), decl.pos) + } + + var membersToCheck: util.SimpleIdentityMap[Name, Type] = util.SimpleIdentityMap.Empty[Name] + val seenClasses = new util.HashSet[Symbol](256) + + def parents(cls: Symbol) = + cls.info.parents.map(_.classSymbol) + .filter(_.is(AbstractOrTrait)) + .dropWhile(_.is(JavaDefined | Scala2x)) + + def addDecls(cls: Symbol): Unit = + if (!seenClasses.contains(cls)) { + seenClasses.addEntry(cls) + for (mbr <- cls.info.decls) + if (mbr.isTerm && mbr.is(Deferred | Method) && + (mbr.hasAnnotation(defn.PartialAnnot) || mbr.hasAnnotation(defn.FilledAnnot)) && + !membersToCheck.contains(mbr.name)) + membersToCheck = membersToCheck.updated(mbr.name, mbr.info.asSeenFrom(self, mbr.owner)) + parents(cls).foreach(addDecls) + } + parents(cls).foreach(addDecls) // no need to check methods defined in current class + + def invalidImplementMsg(sym: Symbol) = + s"""|@scala.annotation.partial required for ${sym.show} in ${sym.owner.show} + |Because the abstract method it implements is marked as `@partial` or `@filled`.""" + .stripMargin + + for (name <- membersToCheck.keys) { + val tp = membersToCheck(name) + for { + mbrd <- self.member(name).alternatives + if mbrd.info.overrides(tp, matchLoosely = true) + } { + val mbr = mbrd.symbol + if (mbr.owner.ne(cls) && + !mbr.isOverride && + !mbr.hasAnnotation(defn.PartialAnnot) && + !mbr.hasAnnotation(defn.FilledAnnot) ) + ctx.warning(invalidImplementMsg(mbr), cls.pos) + } + } + + checkInit(cls, tree) + + tree + } + + def checkInit(cls: ClassSymbol, tmpl: tpd.Template)(implicit ctx: Context) = { + debug("*************************************") + debug("checking " + cls.show) + debug("*************************************") + + val analyzer = new Analyzer + + // partial check + partialCheck(cls, tmpl, analyzer) + + // current class env needs special setup + val root = Heap.createRootEnv + val obj = new ObjectValue(tp = cls.typeRef, open = !cls.is(Final) && !cls.isAnonymousClass) + // enhancement possible to check if there are actual children + // and whether children are possible in other modules. + + // for recursive usage + root.addClassDef(cls, tmpl) + indexOuter(cls, root) + + // init check + val constr = tmpl.constr + val values = constr.vparamss.flatten.map { param => param.tpe.widen.value } + val poss = constr.vparamss.flatten.map(_.pos) + val res = root.init(constr.symbol, values, poss, cls.pos, obj, analyzer) + + val sliceValue = obj.slices(cls).asInstanceOf[SliceValue] + val slice = root.heap(sliceValue.id).asSlice + + res.effects.foreach(_.report) + slice.notAssigned.foreach { sym => + if (!sym.is(Deferred)) ctx.warning(s"field ${sym.name} is not initialized", sym.pos) + } + + // filled check: try commit early + if (obj.open || slice.widen != FullValue) filledCheck(obj, tmpl, root.heap) + } + + def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { + val obj = new ObjectValue(tp = cls.typeRef, open = !cls.is(Final) && !cls.isAnonymousClass) + // enhancement possible to check if there are actual children + // and whether children are possible in other modules. + + val root = Heap.createRootEnv + val heap = root.heap + val slice = root.newSlice(cls) + analyzer.indexMembers(tmpl.body, slice) + slice.innerEnv.add(cls, obj) + indexOuter(cls, root) + + obj.add(cls, new SliceValue(slice.id)) + cls.baseClasses.tail.foreach(base => obj.add(base, PartialValue)) + + if (!cls.is(Trait)) + tmpl.constr.vparamss.flatten.zipWithIndex.foreach { case (param: ValDef, index) => + val sym = cls.info.member(param.name).suchThat(x => !x.is(Method)).symbol + if (sym.exists) slice.add(sym, sym.info.value) + } + + def checkMethod(sym: Symbol): Unit = { + if (!sym.isPartial && !sym.isOverride) return + + val heap2 = heap.clone + var res = obj.select(sym, heap2, sym.pos) + if (!sym.info.isParameterless) + res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + + if (res.hasErrors) { + ctx.warning("Calling the partial method causes errors", sym.pos) + res.effects.foreach(_.report) + } + else if (res.value != FullValue) { + ctx.warning("Partial method must return a full value", sym.pos) + } + } + + def checkLazy(sym: Symbol): Unit = { + if (!sym.isPartial && !sym.isOverride) return + + val heap2 = heap.clone + val res = obj.select(sym, heap2, sym.pos) + if (res.hasErrors) { + ctx.warning("Forcing partial lazy value causes errors", sym.pos) + res.effects.foreach(_.report) + } + else { + val value = res.value.widen(heap2, sym.pos) + if (value != FullValue) ctx.warning("Partial lazy value must return a full value", sym.pos) + } + } + + tmpl.body.foreach { + case ddef: DefDef if !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => + checkMethod(ddef.symbol) + case vdef: ValDef if vdef.symbol.is(Lazy) => + checkLazy(vdef.symbol) + case _ => + } + } + + def filledCheck(obj: ObjectValue, tmpl: tpd.Template, heap: Heap)(implicit ctx: Context) = { + def checkMethod(sym: Symbol): Unit = { + if (sym.isPartial || sym.isOverride || !sym.isFilled) return + + var res = obj.select(sym, heap, sym.pos) + if (!sym.info.isParameterless) + res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + if (res.hasErrors) { + ctx.warning("Calling the filled method causes errors", sym.pos) + res.effects.foreach(_.report) + } + else if (res.value != FullValue) { + ctx.warning("Filled method must return a full value", sym.pos) + } + } + + def checkLazy(sym: Symbol): Unit = { + if (sym.isPartial || sym.isOverride || !sym.isFilled) return + + val res = obj.select(sym, heap, sym.pos) + if (res.hasErrors) { + ctx.warning("Forcing filled lazy value causes errors", sym.pos) + res.effects.foreach(_.report) + } + else { + val value = res.value.widen(heap, sym.pos) + if (value != FullValue) ctx.warning("Filled lazy value must return a full value", sym.pos) + } + } + + def checkValDef(sym: Symbol): Unit = { + val isOverride = sym.allOverriddenSymbols.exists(sym => sym.isInit) + val expected: OpaqueValue = + if (isOverride) FullValue + else sym.info.value.join(sym.value) + + val actual = obj.select(sym, heap, sym.pos).value.widen(heap, sym.pos) + if (actual < expected) ctx.warning(s"Found = $actual, expected = $expected" , sym.pos) + } + + tmpl.body.foreach { + case ddef: DefDef if ddef.symbol.isFilled && !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => + checkMethod(ddef.symbol) + case vdef: ValDef if vdef.symbol.is(Lazy) => + checkLazy(vdef.symbol) + case vdef: ValDef if !vdef.symbol.hasAnnotation(defn.UncheckedAnnot) => + checkValDef(vdef.symbol) + case _ => + } + } + + + def indexOuter(cls: ClassSymbol, env: Env)(implicit ctx: Context) = { + def recur(cls: Symbol, maxValue: OpaqueValue): Unit = if (cls.owner.exists) { + val outerValue = cls.value + val enclosingCls = cls.owner.enclosingClass + + if (!cls.owner.isClass || maxValue == FullValue) { + env.add(enclosingCls, FullValue) + recur(enclosingCls, FullValue) + } + else { + val meet = outerValue.meet(maxValue) + env.add(enclosingCls, meet) + recur(enclosingCls, meet) + } + } + recur(cls, cls.value) + } +} 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..3a96b27526c1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -0,0 +1,93 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import StdNames._ +import Names._ +import ast._ +import Trees._ +import Symbols._ +import Types._ +import Decorators._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import collection.mutable + +//======================================= +// Res +//======================================= +import Effect._ + +case class Res(var effects: Effects = Vector.empty, var value: Value = FullValue) { + def +=(eff: Effect): Unit = effects = effects :+ eff + + def ++=(effs: Effects): Unit = + effects ++= effs + + def +(eff: Effect): this.type = { + effects = effects :+ eff + this + } + + def ++(effs: Effects): this.type = { + effects ++= effs + this + } + + def hasErrors = effects.size > 0 + + def join(res2: Res): Res = + Res( + effects = res2.effects ++ this.effects, + value = res2.value.join(value) + ) + + def show(setting: ShowSetting)(implicit ctx: Context): String = + s"""~Res( + ~| effects = ${if (effects.isEmpty) "()" else effects.mkString("\n| - ", "\n| - ", "")} + ~| value = ${value.show(setting)} + ~)""" + .stripMargin('~') +} + + +//======================================= +// Effects +//======================================= + +sealed trait Effect { + def report(implicit ctx: Context): Unit = this match { + case Uninit(sym, pos) => + ctx.warning(s"Reference to uninitialized value `${sym.name}`", pos) + case OverrideRisk(sym, pos) => + ctx.warning(s"Reference to $sym which could be overriden. Consider make the method final or annotate it with `@partial` or `@filled` for safe overriding", pos) + case Call(sym, effects, pos) => + ctx.warning(s"The call to `${sym.name}` causes initialization problem", pos) + effects.foreach(_.report) + case Force(sym, effects, pos) => + ctx.warning(s"Forcing lazy val `${sym.name}` causes initialization problem", pos) + effects.foreach(_.report) + case Instantiate(cls, effs, pos) => + ctx.warning(s"Create instance results in initialization errors", pos) + effs.foreach(_.report) + case UseAbstractDef(sym, pos) => + ctx.warning(s"`@scala.annotation.init` is recommended for abstract $sym for safe initialization", sym.pos) + ctx.warning(s"Reference to abstract $sym which should be annotated with `@scala.annotation.init`", pos) + case Generic(msg, pos) => + ctx.warning(msg, pos) + } +} + +case class Uninit(sym: Symbol, pos: Position) extends Effect // usage of uninitialized values +case class OverrideRisk(sym: Symbol, pos: Position) extends Effect // calling methods that are not override-free +case class Call(sym: Symbol, effects: Seq[Effect], pos: Position) extends Effect // calling method results in error +case class Force(sym: Symbol, effects: Seq[Effect], pos: Position) extends Effect // force lazy val results in error +case class Instantiate(cls: Symbol, effs: Seq[Effect], pos: Position) extends Effect // create new instance of in-scope inner class results in error +case class UseAbstractDef(sym: Symbol, pos: Position) extends Effect // use abstract def during initialization, see override5.scala +case class Generic(msg: String, pos: Position) extends Effect // generic problem + +object Effect { + type Effects = Vector[Effect] +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala new file mode 100644 index 000000000000..91511ef26d7b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -0,0 +1,322 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import StdNames._ +import Names._ +import ast._ +import tpd._ +import Symbols._ +import Types._ +import Decorators._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import collection.mutable +import annotation.internal.sharable + +//======================================= +// Heap / Env +//======================================= + +trait HeapEntry extends Cloneable { + val id: Int = Heap.uniqueId + var heap: Heap = null + + override def clone: HeapEntry = super.clone.asInstanceOf[HeapEntry] + + def asEnv: Env = this.asInstanceOf[Env] + def asSlice: SliceRep = this.asInstanceOf[SliceRep] +} + +object Heap { + @sharable private var _uniqueId = 0 + def uniqueId: Int = { + _uniqueId += 1 + _uniqueId + } + + class RootEnv extends Env(-1) { + override def contains(sym: Symbol): Boolean = _syms.contains(sym) + + override def containsClass(cls: ClassSymbol): Boolean = + _classInfos.contains(cls) + } + + def createRootEnv: Env = { + val heap = new Heap + val env = new RootEnv + heap.add(env) + env + } + + def join(entry1: HeapEntry, entry2: HeapEntry): HeapEntry = (entry1, entry2) match { + case (env1: Env, env2: Env) => + env1.join(env2) + case (s1: SliceRep, s2: SliceRep) => // caller ensures `s1.id = s2.id` + s1.join(s2) + case _ => + throw new Exception(s"Cannot join $entry1 and $entry2") + } +} + +class Heap extends Cloneable { + private var _parent: Heap = null + protected var _entries: mutable.Map[Int, HeapEntry] = mutable.Map() + + def apply(id: Int) =_entries(id) + + def contains(id: Int) = _entries.contains(id) + + def add(entry: HeapEntry) = { + entry.heap = this + _entries(entry.id) = entry + } + + override def clone: Heap = { + val heap = new Heap + heap._parent = this + heap._entries = mutable.Map() + + this._entries.foreach { case (id, entry) => + val entry2: HeapEntry = entry.clone + entry2.heap = heap + heap._entries(id) = entry2 + } + + heap + } + + def join(heap2: Heap)(implicit ctx: Context): Heap = { + assert(heap2._parent `eq` this) + heap2._entries.foreach { case (id, entry) => + if (this.contains(id)) + this._entries(id) = Heap.join(this(id), entry) + else { + entry.heap = this + this._entries(id) = entry + } + } + this + } + + def show: String = + _entries.keys.mkString("[", ", ", "]") +} + +//======================================= +// environment +//======================================= + +/** The state of closure and objects + * + * @param outerId required for modelling closures + * + * Invariants: + * 1. the data stored in the immutable map must be immutable + * 2. environment refer each other via `id`, which implies values should + * never use captured environment other than its `id`. + */ +class Env(outerId: Int) extends HeapEntry { + assert(outerId != id) + + /** local symbols defined in current scope */ + protected var _syms: Map[Symbol, Value] = Map() + + /** local class definitions */ + protected var _classInfos: Map[ClassSymbol, Template] = Map() + def addClassDef(cls: ClassSymbol, tmpl: Template) = + _classInfos = _classInfos.updated(cls, tmpl) + def containsClass(cls: ClassSymbol): Boolean = + _classInfos.contains(cls) || outer.containsClass(cls) + def getClassDef(cls: ClassSymbol): Template = + if (_classInfos.contains(cls)) _classInfos(cls) + else outer.getClassDef(cls) + + def outer: Env = heap(outerId).asInstanceOf[Env] + + def fresh(heap: Heap = this.heap): Env = { + val env = new Env(this.id) + heap.add(env) + env + } + + def newSlice(cls: ClassSymbol, heap: Heap = this.heap): SliceRep = { + val innerEnv = fresh(heap) + val slice = new SliceRep(cls, innerEnvId = innerEnv.id) + heap.add(slice) + slice + } + + def apply(sym: Symbol): Value = + if (_syms.contains(sym)) _syms(sym) + else outer(sym) + + def add(sym: Symbol, value: Value) = + _syms = _syms.updated(sym, value) + + def update(sym: Symbol, value: Value): Unit = + if (_syms.contains(sym)) _syms = _syms.updated(sym, value) + else outer.update(sym, value) + + def contains(sym: Symbol): Boolean = _syms.contains(sym) || outer.contains(sym) + + def notAssigned = _syms.keys.filter(sym => _syms(sym) == NoValue) + def notForcedSyms = _syms.keys.filter(sym => _syms(sym).isInstanceOf[LazyValue]) + + def join(env2: Env): Env = { + assert(this.id == env2.id) + + _syms.foreach { case (sym: Symbol, value: Value) => + assert(env2.contains(sym)) + val value2 = env2._syms(sym) + _syms = _syms.updated(sym, value.join(value2)) + } + + this + } + + /** Assign to a local variable, i.e. TermRef with NoPrefix */ + def assign(sym: Symbol, value: Value, pos: Position)(implicit ctx: Context): Res = + if (this.contains(sym)) { + this(sym) = value + Res() + } + else if (value.widen(this.heap, pos) != FullValue) // leak assign + Res(effects = Vector(Generic("Cannot leak an object under initialization", pos))) + else Res() + + + /** Select a local variable, i.e. TermRef with NoPrefix */ + def select(sym: Symbol, pos: Position)(implicit ctx: Context): Res = + if (this.contains(sym)) { + val value = this(sym) + if (sym.is(Flags.Lazy)) { + if (value.isInstanceOf[LazyValue]) { + val res = value(Nil, Nil, pos, this.heap) + this(sym) = res.value + + if (res.hasErrors) Res(effects = Vector(Force(sym, res.effects, pos))) + else Res(value = res.value) + } + else Res(value = value) + } + else if (sym.is(Flags.Method)) { + if (sym.info.isInstanceOf[ExprType]) { // parameter-less call + value(Nil, Nil, pos, this.heap) + } + else Res(value = value) + } + else { + var effs = Vector.empty[Effect] + if (value == NoValue) Res(effects = effs :+ Uninit(sym, pos)) + else Res(value = value) + } + } + else if (sym.isClass && this.containsClass(sym.asClass)) Res() + else { + // How do we know the class/method/field does not capture/use a partial/filled outer? + // If method/field exist, then the outer class beyond the method/field is full, + // i.e. external methods/fields/classes are always safe. + FullValue.select(sym, this.heap, pos) + } + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, indexer: Indexer)(implicit ctx: Context): Res = { + val cls = constr.owner.asClass + if (this.containsClass(cls)) { + val tmpl = this.getClassDef(cls) + indexer.init(constr, tmpl, values, argPos, pos, obj, this) + } + else FullValue.init(constr, values, argPos, pos, obj, heap, indexer) + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = { + def members = _syms.map { case (k, v) => k.show + " ->" + setting.indent(v.show(setting), tabs = 2) }.mkString("\n") + (if (outerId > 0) outer.show(setting) + "\n" else "") ++ + s"-------------- $id($outerId) ---------------------\n${members}" + } +} + +/** A container holds all information about fields of an class slice of an object + */ +class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Cloneable { + override def clone: SliceRep = super.clone.asInstanceOf[SliceRep] + + def innerEnv: Env = heap(innerEnvId).asEnv + + /** inner class definitions */ + private var _classInfos: Map[ClassSymbol, Template] = Map() + def add(cls: ClassSymbol, info: Template) = _classInfos = + _classInfos.updated(cls, info) + def classInfos: Map[ClassSymbol, Template] = _classInfos + + /** methods and fields of the slice */ + private var _syms: Map[Symbol, Value] = Map() + + def symbols: Map[Symbol, Value] = _syms + + def apply(sym: Symbol): Value = + _syms(sym) + + def add(sym: Symbol, value: Value) = + _syms = _syms.updated(sym, value) + + def remove(sym: Symbol) = + _syms = _syms - sym + + def update(sym: Symbol, value: Value): Unit = { + assert(_syms.contains(sym)) + _syms = _syms.updated(sym, value) + } + + def contains(sym: Symbol): Boolean = + _syms.contains(sym) + + def notAssigned = _syms.keys.filter(sym => _syms(sym) == NoValue) + def notForcedSyms = _syms.keys.filter(sym => _syms(sym).isInstanceOf[LazyValue]) + + // Invariant: two slices with the same id always have the same `classInfos`, + // thus they can be safely ignored in `join`. + def join(obj2: SliceRep): SliceRep = { + assert(this.id == obj2.id) + + _syms.foreach { case (sym: Symbol, value: Value) => + assert(obj2.contains(sym)) + val value2 = obj2._syms(sym) + _syms = _syms.updated(sym, value.join(value2)) + } + + this + } + + override def equals(that: Any): Boolean = that match { + case that: SliceRep => that.id == this.id + case _ => false + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = { + if (setting.printed.contains(id)) return "id: " + id + + setting.printed += id + def members = _syms.map { case (k, v) => + k.show + " -> " + setting.indent(v.show(setting), tabs = 2) + }.mkString("\n") + + s"\n id: $id($innerEnvId)\n${setting.indent(members, tabs = 1)}" + } + + def widen(implicit ctx: Context): OpaqueValue = { + def isPartialOrFilled(value: Value): Boolean = + value == PartialValue || value == FilledValue + + if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) PartialValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isPartial || sym.info.isFilled) }) FilledValue + else { + // check outer + val owner = cls.owner + if (!owner.isClass) FullValue + else innerEnv(owner).widen(heap, NoPosition) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala new file mode 100644 index 000000000000..e2154f774137 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -0,0 +1,138 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import MegaPhase._ +import Contexts.Context +import StdNames._ +import Names._ +import Phases._ +import ast._ +import Trees._ +import Flags._ +import SymUtils._ +import Symbols._ +import Denotations._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import Constants.Constant +import collection.mutable + + +trait Indexer { self: Analyzer => + import tpd._ + + def methodValue(ddef: DefDef, env: Env)(implicit ctx: Context): FunctionValue = + new FunctionValue { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = + if (isChecking(ddef.symbol)) { + // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. + debug(s"recursive call of ${ddef.symbol} found") + Res() + } + else { + val env2 = env.fresh(heap) + + ddef.vparamss.flatten.zipWithIndex.foreach { case (param: ValDef, index) => + env2.add(param.symbol, value = values(index)) + } + val res = checking(ddef.symbol) { self.apply(ddef.rhs, env2)(ctx.withOwner(ddef.symbol)) } + if (res.hasErrors) res.effects = Vector(Call(ddef.symbol, res.effects, pos)) + res + } + } + + def lazyValue(vdef: ValDef, env: Env)(implicit ctx: Context): LazyValue = + new LazyValue { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = + if (isChecking(vdef.symbol)) { + // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. + debug(s"recursive call of ${vdef.symbol} found") + Res() + } + else { + val env2 = heap(env.id).asEnv + val res = checking(vdef.symbol) { self.apply(vdef.rhs, env2)(ctx.withOwner(vdef.symbol)) } + if (res.hasErrors) res.effects = Vector(Force(vdef.symbol, res.effects, pos)) + res + } + } + + /** Index local definitions */ + def indexStats(stats: List[Tree], env: Env)(implicit ctx: Context): Unit = stats.foreach { + case ddef: DefDef if !ddef.symbol.isConstructor => // TODO: handle secondary constructor + env.add(ddef.symbol, methodValue(ddef, env)) + case vdef: ValDef if vdef.symbol.is(Lazy) => + env.add(vdef.symbol, lazyValue(vdef, env)) + case vdef: ValDef => + env.add(vdef.symbol, NoValue) + case tdef: TypeDef if tdef.isClassDef => + // class has to be handled differently because of inheritance + env.addClassDef(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) + case _ => + } + + /** Index member definitions + * + * trick: use `slice` for name resolution, but `env` for method execution + */ + def indexMembers(stats: List[Tree], slice: SliceRep)(implicit ctx: Context): Unit = stats.foreach { + case ddef: DefDef => + slice.add(ddef.symbol, methodValue(ddef, slice.innerEnv)) + case vdef: ValDef if vdef.symbol.is(Lazy) => + slice.add(vdef.symbol, lazyValue(vdef, slice.innerEnv)) + case vdef: ValDef => + val value = if (vdef.symbol.isInit) FullValue else NoValue + slice.add(vdef.symbol, value) + case tdef: TypeDef if tdef.isClassDef => + // class has to be handled differently because of inheritance + slice.add(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) + case _ => + } + + def init(constr: Symbol, tmpl: Template, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, env: Env)(implicit ctx: Context): Res = { + val cls = constr.owner.asClass + + if (isChecking(cls)) { + debug(s"recursive creation of $cls found") + Res() + } + else checking(cls) { + val slice = env.newSlice(cls) + obj.add(cls, new SliceValue(slice.id)) + + // The outer of parents are set (but not recursively) + // before any super-calls if they are known. + // This is not specified in the Scala specification. + // Calling methods of an unrelated trait during initialization + // is dangerous, thus should be discouraged. Therefore, the analyzer + // doesn't follow closely the semantics here. + + // first index current class + indexMembers(tmpl.body, slice) + + // propagate constructor arguments + tmpl.constr.vparamss.flatten.zipWithIndex.foreach { case (param: ValDef, index) => + val sym = cls.info.member(param.name).suchThat(x => !x.is(Method)).symbol + if (sym.exists) slice.add(sym, values(index)) + slice.innerEnv.add(param.symbol, values(index)) + } + + // setup this + slice.innerEnv.add(cls, obj) + + // call parent constructor + val res = checkParents(cls, tmpl.parents, slice.innerEnv, obj)(ctx.withOwner(cls.owner)) + if (res.hasErrors) return res + + // check current class body + res ++= checkStats(tmpl.body, slice.innerEnv)(ctx.withOwner(cls)).effects + res + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala b/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala new file mode 100644 index 000000000000..f68b6771c925 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala @@ -0,0 +1,18 @@ +package dotty.tools.dotc +package transform +package init + +import collection.mutable + +case class ShowSetting(heap: Heap, printed: mutable.Set[Int] = mutable.Set()) { + def indent(content: String, tabs: Int = 1): String = ShowSetting.pad(content, tabs, padFirst = true) +} + +object ShowSetting { + val indentTab = " " + + def pad(s: String, tabs: Int = 1, padFirst: Boolean = false) = { + val padding = indentTab * tabs + s.split("\n").mkString(if (padFirst) padding else "", "\n" + padding, "") + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala new file mode 100644 index 000000000000..0b00870ccb89 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -0,0 +1,594 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import StdNames._ +import Names._ +import NameKinds.DefaultGetterName +import ast._ +import Trees._ +import Symbols._ +import Types._ +import Decorators._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import collection.mutable + +//======================================= +// values +//======================================= + +object Value { + def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + paramInfos.zipWithIndex.foreach { case (tp, index) => + val value = scala.util.Try(values(index)).getOrElse(FullValue) + val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) + if (value.widen(heap, pos) < tp.value) + return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) + } + Res() + } + + def defaultFunctionValue(methSym: Symbol)(implicit ctx: Context): Value = { + assert(methSym.is(Flags.Method)) + if (methSym.info.paramNamess.isEmpty) FullValue + else new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val paramInfos = methSym.info.paramInfoss.flatten + checkParams(methSym, paramInfos, values, argPos, pos, heap) + } + } + } +} + +/** Abstract values in analysis */ +sealed trait Value { + /** Select a member on a value */ + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res + + /** Assign on a value */ + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res + + /** Index an inner class with current value as the immediate outer */ + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res + + /** Apply a method or function to the provided arguments */ + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res + + def show(setting: ShowSetting)(implicit ctx: Context): String + + /** Join two values + * + * NoValue < Partial < Filled < Full + */ + def join(other: Value): Value = (this, other) match { + case (FullValue, v) => v + case (v, FullValue) => v + case (NoValue, _) => NoValue + case (_, NoValue) => NoValue + case (PartialValue, _) => PartialValue + case (_, PartialValue) => PartialValue + case (v1: OpaqueValue, v2: OpaqueValue) => v1.join(v2) + case (o1: ObjectValue, o2: ObjectValue) if o1 `eq` o2 => o1 + case (f1: FunctionValue, f2: FunctionValue) if f1 `eq` f2 => f1 + case (f1: FunctionValue, f2: FunctionValue) => f1.join(f2) + case (o1: SliceValue, o2: SliceValue) => + if (o1.id == o2.id) o1 + else new UnionValue(Set(o1, o2)) + case (v1: LazyValue, v2: LazyValue) if v1 == v2 => v1 + case (v1: UnionValue, v2: UnionValue) => v1 ++ v2 + case (uv: UnionValue, v: SingleValue) => uv + v + case (v: SingleValue, uv: UnionValue) => uv + v + case (v1: SingleValue, v2: SingleValue) => UnionValue(Set(v1, v2)) + } + + /** Widen the value to an opaque value + * + * Widening is needed at analysis boundary. + */ + def widen(heap: Heap, pos: Position, handler: Vector[Effect] => Unit = effs => ())(implicit ctx: Context): OpaqueValue = { + def recur(value: Value, heap: Heap): OpaqueValue = value match { + case ov: OpaqueValue => ov + case fv: FunctionValue => + val testHeap = heap.clone + val res = fv(i => FullValue, i => NoPosition, pos, testHeap) + if (res.hasErrors) { + handler(res.effects) + FilledValue + } + else recur(res.value, testHeap) + case sv: SliceValue => + heap(sv.id).asSlice.widen + case ov: ObjectValue => + if (ov.open) FilledValue + else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => + if (acc != FullValue) return FilledValue + recur(v, heap).join(acc) + } + case UnionValue(vs) => + vs.foldLeft(FullValue: OpaqueValue) { (acc, v) => + if (v == PartialValue || acc == PartialValue) return PartialValue + else acc.join(recur(v, heap)) + } + // case NoValue => NoValue + case _ => // impossible + ??? + } + + recur(this, heap) + } +} + +/** The value is absent */ +object NoValue extends Value { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + + def show(setting: ShowSetting)(implicit ctx: Context): String = "NoValue" +} + +/** A single value, instead of a union value */ +sealed trait SingleValue extends Value + +/** Union of values */ +case class UnionValue(val values: Set[SingleValue]) extends Value { + def apply(args: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + values.foldLeft(Res()) { (acc, value) => + value.apply(args, argPos, pos, heap).join(acc) + } + } + + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + values.foldLeft(Res()) { (acc, value) => + value.select(sym, heap, pos).join(acc) + } + } + + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + values.foldLeft(Res()) { (acc, value) => + value.assign(sym, value, heap, pos).join(acc) + } + } + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + values.foldLeft(Res()) { (acc, value) => + value.init(constr, values, argPos, pos, obj, heap, indexer).join(acc) + } + } + + def +(value: SingleValue): UnionValue = UnionValue(values + value) + def ++(uv: UnionValue): UnionValue = UnionValue(values ++ uv.values) + + def show(setting: ShowSetting)(implicit ctx: Context): String = + "Or{" + setting.indent(values.map(v => v.show(setting)).mkString(", ")) + "}" +} + +/** Values that are subject to type checking rather than analysis */ +abstract sealed class OpaqueValue extends SingleValue { + // not supported + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + + def <(that: OpaqueValue): Boolean = (this, that) match { + case (FullValue, _) => false + case (FilledValue, PartialValue | FilledValue) => false + case (PartialValue, PartialValue) => false + case _ => true + } + + def join(that: OpaqueValue): OpaqueValue = + if (this < that) this else that + + def meet(that: OpaqueValue): OpaqueValue = + if (this < that) that else this +} + +object FullValue extends OpaqueValue { + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = + if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) + else Res() + + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = + if (value.widen(heap, pos) != FullValue) + Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", pos))) + else Res() + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val cls = constr.owner.asClass + val paramInfos = constr.info.paramInfoss.flatten + val res = Value.checkParams(cls, paramInfos, values, argPos, pos, heap) + if (res.hasErrors) return res + + val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(FullValue)) + if (args.exists(_.widen(heap, pos) < FullValue)) obj.add(cls, FilledValue) + + Res() + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = "Full" + + override def toString = "full value" +} + +object PartialValue extends OpaqueValue { + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + // set state to Full, don't report same error message again + val res = Res(value = FullValue) + + if (sym.is(Flags.Method)) { + if (!sym.isPartial && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@partial` in order to be called", pos) + + res.value = Value.defaultFunctionValue(sym) + } + else if (sym.is(Flags.Lazy)) { + if (!sym.isPartial) + res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", pos) + } + else if (sym.isClass) { + if (!sym.isPartial) + res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", pos) + } + else { // field select + if (!sym.isPrimaryConstructorFields || sym.owner.is(Flags.Trait)) + res += Generic(s"Cannot access field $sym on a partial object", pos) + } + + res + } + + /** assign to partial is always fine? */ + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = Res() + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val paramInfos = constr.info.paramInfoss.flatten + val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + if (res.hasErrors) return res + + val cls = constr.owner.asClass + if (!cls.isPartial) { + res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", pos) + res.value = FullValue + return res + } + + obj.add(cls, FilledValue) + + Res() + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = "Partial" + + override def toString = "partial value" +} + +object FilledValue extends OpaqueValue { + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + val res = Res() + if (sym.is(Flags.Method)) { + if (!sym.isPartial && !sym.isFilled && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@partial` or `@filled` in order to be called", pos) + + res.value = Value.defaultFunctionValue(sym) + } + else if (sym.is(Flags.Lazy)) { + if (!sym.isPartial && !sym.isFilled) + res += Generic(s"The lazy field $sym should be marked as `@partial` or `@filled` in order to be accessed", pos) + + res.value = sym.info.value + } + else { + res.value = sym.value.join(sym.info.value) + } + + res + } + + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = + if (value.widen(heap, pos) < sym.info.value) + Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", pos))) + else Res() + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val paramInfos = constr.info.paramInfoss.flatten + val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + if (res.hasErrors) return res + + val cls = constr.owner.asClass + if (!cls.isPartial && !cls.isFilled) { + res += Generic(s"The nested $cls should be marked as `@partial` or `@filled` in order to be instantiated", pos) + res.value = FullValue + return res + } + + obj.add(cls, FilledValue) + + Res() + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = "Filled" + + override def toString = "filled value" +} + +/** A function value or value of method select */ +abstract class FunctionValue extends SingleValue { self => + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res + + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = sym.name match { + case nme.apply | nme.lift => Res(value = this) + case nme.compose => + val selectedFun = new FunctionValue() { + def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val composedFun = new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val arg = values(0) + val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol + val res1 = fun(0).select(applySym, heap, pos) + val res2 = res1.value.apply(arg :: Nil, argPos, pos, heap) + val res3 = self.apply(res2.value :: Nil, argPos, pos, heap) + Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) + } + } + Res(value = composedFun) + } + } + Res(value = selectedFun) + case nme.andThen => + val selectedFun = new FunctionValue() { + def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val composedFun = new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val arg = values(0) + val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol + val res2 = fun(0).select(applySym, heap, pos) + val res3 = res2.value.apply(res2.value :: Nil, argPos, pos, heap) + Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) + } + } + Res(value = composedFun) + } + } + Res(value = selectedFun) + case nme.applyOrElse => + val selectedFun = new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val arg = values(0) + val fun = values(1) + val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol + val res2 = fun.select(applySym, heap, pos) + val res3 = res2.value.apply(arg :: Nil, argPos, pos, heap) + Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) + } + } + Res(value = selectedFun) + case nme.runWith => + val selectedFun = new FunctionValue() { + def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val composedFun = new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val arg = values(0) + val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol + val res2 = fun(0).select(applySym, heap, pos) + val res3 = res2.value.apply(res2.value :: Nil, argPos, pos, heap) + Res(value = FullValue, effects = res1.effects ++ res2.effects ++ res3.effects) + } + } + Res(value = composedFun) + } + } + Res(value = selectedFun) + case nme.orElse => + val selectedFun = new FunctionValue() { + def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val composedFun = new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val arg = values(0) + val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol + val res2 = fun(0).select(applySym, heap, pos) + val res3 = res2.value.apply(arg :: Nil, argPos, pos, heap) + Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) + } + } + Res(value = composedFun) + } + } + Res(value = selectedFun) + case _ => + FullValue.select(sym, heap, pos) + } + + /** not supported */ + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + + def show(setting: ShowSetting)(implicit ctx: Context): String = toString + + override def toString: String = "Function@" + hashCode + + def join(that: FunctionValue): FunctionValue = + new FunctionValue { + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + val heap2 = heap.clone + val res1 = self(values, argPos, pos, heap) + val res2 = that(values, argPos, pos, heap2) + heap.join(heap2) + res1.join(res2) + } + } + +} + +/** A lazy value */ +abstract class LazyValue extends SingleValue { + // not supported + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + + def show(setting: ShowSetting)(implicit ctx: Context): String = toString + + override def toString: String = "LazyValue@" + hashCode +} + +/** A slice of an object */ +class SliceValue(val id: Int) extends SingleValue { + /** not supported, impossible to apply an object value */ + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + val slice = heap(id).asSlice + val value = slice(sym) + + if (sym.is(Flags.Lazy)) { + if (value.isInstanceOf[LazyValue]) { + val res = value(Nil, Nil, pos, heap) + slice(sym) = res.value + res + } + else Res(value = value) + } + else if (sym.is(Flags.Method)) { + if (sym.info.isParameterless) { // parameter-less call + value(Nil, Nil, pos, heap) + } + else Res(value = value) + } + else { + if (value == NoValue) { + if (sym.info.isInstanceOf[ConstantType]) Res() + else Res(effects = Vector(Uninit(sym, pos))) + } + else { + val res = Res(value = value) + + if (sym.is(Flags.Deferred) && !sym.hasAnnotation(defn.InitAnnot)) + res += UseAbstractDef(sym, pos) + + res + } + } + } + + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + val slice = heap(id).asSlice + slice(sym) = value + Res() + } + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val cls = constr.owner.asClass + val slice = heap(id).asSlice + val tmpl = slice.classInfos(cls) + indexer.init(constr, tmpl, values, argPos, pos, obj, slice.innerEnv) + } + + override def hashCode = id + + override def equals(that: Any) = that match { + case that: SliceValue => that.id == id + case _ => false + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = setting.heap(id).asSlice.show(setting) +} + +class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { + /** slices of the object */ + private var _slices: Map[ClassSymbol, Value] = Map() + def slices: Map[ClassSymbol, Value] = _slices + + def add(cls: ClassSymbol, value: Value) = { + if (slices.contains(cls)) { + _slices = _slices.updated(cls, _slices(cls).join(value)) + } + else _slices = _slices.updated(cls, value) + } + + // handle dynamic dispatch + private def resolve(sym: Symbol)(implicit ctx: Context): Symbol = { + if (sym.isClass || sym.isConstructor || sym.isEffectivelyFinal || sym.is(Flags.Private)) sym + else { + // the method may crash, see tests/pos/t7517.scala + try sym.matchingMember(tp) catch { case _: Throwable => NoSymbol } + } + } + + /** not supported, impossible to apply an object value */ + def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + + def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + val target = resolve(sym) + + // select on self type + if (!target.exists) { + if (sym.owner.is(Flags.Trait)) + return PartialValue.select(sym, heap, pos) + else + return FilledValue.select(sym, heap, pos) + } + + if (this.widen(heap, pos) == FullValue) return FullValue.select(sym, heap, pos) + + val cls = target.owner.asClass + if (slices.contains(cls)) { + val res = slices(cls).select(target, heap, pos) + // ignore field access, but field access in Scala + // are method calls, thus is unsafe as well + if (open && target.is(Flags.Method, butNot = Flags.Lazy) && + !target.isPartial && + !target.isFilled && + !target.isOverride && + !target.isEffectivelyFinal && + !target.name.is(DefaultGetterName)) + res += OverrideRisk(target, pos) + res + } + else { + // select on unknown super + assert (target.isDefinedOn(tp)) + FilledValue.select(target, heap, pos) + } + } + + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + val target = resolve(sym) + + // select on self type + if (!target.exists) return PartialValue.assign(sym, value, heap, pos) + + val cls = target.owner.asClass + if (slices.contains(cls)) { + slices(cls).assign(target, value, heap, pos) + } + else { + // select on unknown super + assert(target.isDefinedOn(tp)) + FilledValue.assign(target, value, heap, pos) + } + } + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val cls = constr.owner.asClass + val outerCls = cls.owner.asClass + if (slices.contains(outerCls)) { + slices(outerCls).init(constr, values, argPos, pos, obj, heap, indexer) + } + else { + val value = if (cls.isDefinedOn(tp)) FilledValue else PartialValue + value.init(constr, values, argPos, pos, obj, heap, indexer) + } + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = { + val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") + "Object {\n" + setting.indent(body) + "\n}" + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala new file mode 100644 index 000000000000..24540afa917b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -0,0 +1,61 @@ +package dotty.tools.dotc +package transform + +import core._ +import MegaPhase._ +import Contexts.Context +import StdNames._ +import Names._ +import Phases._ +import ast._ +import Trees._ +import Flags._ +import SymUtils._ +import Symbols._ +import Denotations._ +import SymDenotations._ +import Types._ +import Decorators._ +import util.Positions._ +import Constants.Constant +import collection.mutable + +package object init { + implicit class TypeOps(val tp: Type) extends AnyVal { + def isPartial(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.PartialAnnot) + def isFilled(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.FilledAnnot) + + def value(implicit ctx: Context) = + if (isPartial) PartialValue + else if (isFilled) FilledValue + else FullValue + } + + implicit class SymOps(val sym: Symbol) extends AnyVal { + def isPartial(implicit ctx: Context) = sym.hasAnnotation(defn.PartialAnnot) + def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) + def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) + def isOverride(implicit ctx: Context) = + (sym.is(Method) && sym.allOverriddenSymbols.exists(sym => sym.isPartial || sym.isFilled)) || + (!sym.is(Method) && sym.allOverriddenSymbols.exists(sym => sym.isPartial || sym.isFilled || sym.isInit)) + + def isPrimaryConstructorFields(implicit ctx: Context) = sym.is(ParamAccessor) + + def isDefinedOn(tp: Type)(implicit ctx: Context): Boolean = + tp.classSymbol.isSubClass(sym.owner) + + def value(implicit ctx: Context) = + if (isPartial) PartialValue + else if (isFilled) FilledValue + else FullValue + + def isConcreteField(implicit ctx: Context) = + sym.isTerm && sym.is(AnyFlags, butNot = Deferred | Method | Local | Private) + + def isNonParamField(implicit ctx: Context) = + sym.isTerm && sym.is(AnyFlags, butNot = Method | ParamAccessor | Lazy | Deferred) + + def isField(implicit ctx: Context) = + sym.isTerm && sym.is(AnyFlags, butNot = Method | Lazy | Deferred) + } +} \ 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 4cbbc24d7d7c..7339c02f3df5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -164,9 +164,15 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings") + compileFile("tests/neg-custom-args/i3627.scala", allowDeepSubtypes) + compileFile("tests/neg-custom-args/matchtype-loop.scala", allowDeepSubtypes) + + compileFilesInDir("tests/neg-custom-args/safe-init", defaultOptions and "-Xfatal-warnings") + compileFile("tests/neg-custom-args/completeFromSource/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args", "-scansource")) }.checkExpectedErrors() + @Test def negInit: Unit = { + implicit val testGroup: TestGroup = TestGroup("compileNeg") + compileFilesInDir("tests/neg-custom-args/safe-init", defaultOptions and "-Xfatal-warnings") + }.checkExpectedErrors() + // Run tests ----------------------------------------------------------------- @Test def runAll: Unit = { diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 9cc046127317..6c4dee3ca1fd 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -40,4 +40,7 @@ object DottyPredef { @forceInline final def implicitly[T](implicit ev: T): T = ev @forceInline def locally[T](body: => T): T = body + + type Partial[T] = T @scala.annotation.partial + type Filled[T] = T @scala.annotation.filled } diff --git a/library/src/scala/annotation/filled.scala b/library/src/scala/annotation/filled.scala new file mode 100644 index 000000000000..f77f214f1f7e --- /dev/null +++ b/library/src/scala/annotation/filled.scala @@ -0,0 +1,18 @@ +package scala.annotation + +/** An annotation to indicate that a value may be + * under initialization, but all its fields are + * assigned. + * + * All fields of a filled object are assigned, but + * the fields may refer to objects under initialization. + * In contrast, a partial object may have unassigned + * fields. + * + * When used on methods, it means `this` and `super` + * are filled. + * + * When used on constructors, it means the immediate + * outer is filled. + */ +class filled extends StaticAnnotation diff --git a/library/src/scala/annotation/init.scala b/library/src/scala/annotation/init.scala new file mode 100644 index 000000000000..b6d773bdae75 --- /dev/null +++ b/library/src/scala/annotation/init.scala @@ -0,0 +1,5 @@ +package scala.annotation + +/** An annotation to indicate that an abstract field is used during initialization + */ +class init extends StaticAnnotation diff --git a/library/src/scala/annotation/partial.scala b/library/src/scala/annotation/partial.scala new file mode 100644 index 000000000000..8025907440c2 --- /dev/null +++ b/library/src/scala/annotation/partial.scala @@ -0,0 +1,13 @@ +package scala.annotation + +/** An annotation to indicate that a object may be + * under initialization, i.e. some fields may not + * be assigned yet. + * + * When used on methods, it means `this` and `super` + * are partial. + * + * When used on constructors, it means the immediate + * outer is partial. + */ +class partial extends StaticAnnotation diff --git a/tests/neg-custom-args/isInstanceOf/1828.scala b/tests/neg-custom-args/isInstanceOf/1828.scala index aeb83f1a1070..eacde4641289 100644 --- a/tests/neg-custom-args/isInstanceOf/1828.scala +++ b/tests/neg-custom-args/isInstanceOf/1828.scala @@ -4,6 +4,6 @@ class Test { case a: Int => f(a) } - val t: Int | String = 5 - val t1 = remove[String](t, _.toString) + def t: Int | String = 5 + def t1 = remove[String](t, _.toString) } diff --git a/tests/neg-custom-args/isInstanceOf/3324g.scala b/tests/neg-custom-args/isInstanceOf/3324g.scala index 423e56eee4b7..eb703491ebf5 100644 --- a/tests/neg-custom-args/isInstanceOf/3324g.scala +++ b/tests/neg-custom-args/isInstanceOf/3324g.scala @@ -15,5 +15,5 @@ class Test { case _: B[T] => // should be an error!! } - quux(new C[Int]) + def problem = quux(new C[Int]) } diff --git a/tests/neg-custom-args/safe-init/Properties.scala b/tests/neg-custom-args/safe-init/Properties.scala new file mode 100644 index 000000000000..526a9dd83cfe --- /dev/null +++ b/tests/neg-custom-args/safe-init/Properties.scala @@ -0,0 +1,102 @@ +/* __ *\ +** ________ ___ / / ___ 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 } +import scala.annotation.partial + +private[scala] trait PropertiesTrait { + @partial protected def propCategory: String // specializes the remainder of the values + @partial 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.") + + var x: Int = _ // error +} diff --git a/tests/neg-custom-args/safe-init/cycle.scala b/tests/neg-custom-args/safe-init/cycle.scala new file mode 100644 index 000000000000..87b31cf5bd9d --- /dev/null +++ b/tests/neg-custom-args/safe-init/cycle.scala @@ -0,0 +1,21 @@ +import scala.annotation.filled + +class Parent { + val child: Child @filled = new Child(this) + child.show // error + + val name = "parent" + + def show = child.show +} + +class Child(parent: Partial[Parent]) { + val name = "child" + + println(parent.name) // error + + def show = { + println(parent.name) + println(name) + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/double-list.scala b/tests/neg-custom-args/safe-init/double-list.scala new file mode 100644 index 000000000000..c7194ed61b43 --- /dev/null +++ b/tests/neg-custom-args/safe-init/double-list.scala @@ -0,0 +1,13 @@ +class List { + val sentinel: Node = new Node(null, null, this, null) // error + + def insert(data: AnyRef) = sentinel.insertAfter(data) +} + +class Node(var prev: Node, var next: Node, parent: List, data: AnyRef) { + def insertAfter(data: AnyRef) = { + val node = new Node(this, this.next, this.parent, data) + next.prev = node + next = node + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/double-list2.scala b/tests/neg-custom-args/safe-init/double-list2.scala new file mode 100644 index 000000000000..a6497bd6007f --- /dev/null +++ b/tests/neg-custom-args/safe-init/double-list2.scala @@ -0,0 +1,17 @@ +import scala.annotation.filled + +class List { + val sentinel: Node @filled = new Node(null, null, this, null) + + def insert(data: AnyRef) = sentinel.insertAfter(data) +} + +class Node(var prev: Node, var next: Node, parent: Partial[List], data: AnyRef) { + parent.insert("hello") // error + + def insertAfter(data: AnyRef) = { + val node = new Node(this, this.next, this.parent, data) + next.prev = node + next = node + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/early1.scala b/tests/neg-custom-args/safe-init/early1.scala new file mode 100644 index 000000000000..84df7a5fc578 --- /dev/null +++ b/tests/neg-custom-args/safe-init/early1.scala @@ -0,0 +1,13 @@ +class Parent { + def foo(): Int = 5 +} + +final class Child extends Parent { + val a = 4 + + def g() = foo() // error + g() // error + + val b = 10 + g() // ok +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/early2.scala b/tests/neg-custom-args/safe-init/early2.scala new file mode 100644 index 000000000000..1e58c592089a --- /dev/null +++ b/tests/neg-custom-args/safe-init/early2.scala @@ -0,0 +1,13 @@ +class Parent { + def foo(): Int = 5 +} + +class Child extends Parent { + val a = 4 + + def g() = foo() // error + g() // error + + val b = 10 + g() // error, as `Child` is not final +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/escape1.scala b/tests/neg-custom-args/safe-init/escape1.scala new file mode 100644 index 000000000000..588f0933cfc3 --- /dev/null +++ b/tests/neg-custom-args/safe-init/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/neg-custom-args/safe-init/escape2.scala b/tests/neg-custom-args/safe-init/escape2.scala new file mode 100644 index 000000000000..f23549646b5d --- /dev/null +++ b/tests/neg-custom-args/safe-init/escape2.scala @@ -0,0 +1,10 @@ +class Foo { + new Bar(this) // error + val id = 567 + val b = new Bar(this) // error +} + +class Bar(foo: Foo) { + println(foo.id) + println(foo.b) +} diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala new file mode 100644 index 000000000000..64ce9ed7a099 --- /dev/null +++ b/tests/neg-custom-args/safe-init/example.scala @@ -0,0 +1,46 @@ +import scala.annotation.filled + +class Parent(x: Int) { + var name: String = _ + var addr: String = _ // error: addr is not initialized + + val len = name.size // error: name not initialized + lazy val l1 = name.size // ok: l1 is lazy + lazy val l2 = addr.size // error: l2 is forced at L32 before `addr` is initialized + + val fun: Int => Int = n => n + list.size // ok, fun is a partial value + val bar: Bar @ filled = new Bar("bar", fun) // ok, Bar accepts partial value + bar.result // error: bar is a partial value + + val child = new Child(this) // error: `this` is partial, while full value expected + List(5, 9).map(n => n + list.size) // error: partial value used as full value + + f(20) // error: list not initialized + + val list = List(1, 2, 3) + + if (x > 5) { + name = "big" + addr = "Lausanne" + } + else { + name = "small" + } + + val temp1 = l1 // ok, name init + val temp2 = l2 // error: addr not initialized + + private def f(m: Int) = + m + list.size // error: `f` is called at L19 before `list` is initialized + + List(1, 3, 5).map(n => n + list.size) // ok, `this.list` already initialized +} + + +class Bar(val name: String, fun: Partial[Int => Int]) { + def result = fun(20) +} + +class Child(parent: Parent) { + println(parent.name) +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/flags.scala b/tests/neg-custom-args/safe-init/flags.scala new file mode 100644 index 000000000000..32f72ccb365b --- /dev/null +++ b/tests/neg-custom-args/safe-init/flags.scala @@ -0,0 +1,19 @@ +object Flags { + private final val TERMindex = 0 + private final val TYPEindex = 1 + private final val TERMS = 1 << TERMindex + private final val TYPES = 1 << TYPEindex + + case class FlagSet(val bits: Long) { + def toTermFlags = + if (bits == 0) this + else FlagSet(bits & ~KINDFLAGS | TERMS) // error: triggered from JavaStatic.toTermFlags + } + final val JavaStatic = FlagSet(31) + final val JavaStaticTerm = JavaStatic.toTermFlags // error: KINDFLAGS uninitialized + + private final val KINDFLAGS = identity(TERMS | TYPES) + + final val Private = FlagSet(2) + final val PrivateTerm = Private.toTermFlags // ok +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/flow2.scala b/tests/neg-custom-args/safe-init/flow2.scala new file mode 100644 index 000000000000..aa4c69e50f2e --- /dev/null +++ b/tests/neg-custom-args/safe-init/flow2.scala @@ -0,0 +1,7 @@ +class Foo { + val len = list.size // error + val list = List(4, 6) + + lazy val len2 = list2.size // ok + val list2 = List(4, 6) +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function.scala b/tests/neg-custom-args/safe-init/function.scala new file mode 100644 index 000000000000..3799ab7ad9f7 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function.scala @@ -0,0 +1,12 @@ +class Foo { + val x = "hello" + val fun1: Int => Int = n => 0 + n + list.size // ok + val fun2: Int => Int = n => 1 + n + list.size // error: fun is called in the next line + fun2(5) // error: latent effects + + List(5, 9).map(n => 2 + n + list.size) // error: closure is partial, but a full value expected + + val list = List(1, 2, 3) + + List(5, 9).map(n => 3 + n + list.size) // ok, `this.list` already initialized +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function2.scala b/tests/neg-custom-args/safe-init/function2.scala new file mode 100644 index 000000000000..e7091284efc8 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function2.scala @@ -0,0 +1,12 @@ +class Foo { + private def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below + fun(5) // error: select on partial value + + private def getThis: Foo = this + getThis.x // error + + private def getThis(x: Int): Foo = this + getThis(56).x // error + + val x = "hello" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function3.scala b/tests/neg-custom-args/safe-init/function3.scala new file mode 100644 index 000000000000..0275c764e1d2 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function3.scala @@ -0,0 +1,29 @@ +final class Foo { + def getName1(foo: Foo): String = foo.name // error + getName1(this) // error + + + def getName2(foo: () => String): String = foo() // error + getName2(() => this.name) // error + + def getName2b(foo: () => String): String = "hello" + getName2b(() => this.name) + + def getName3(foo: () => String): () => Int = () => foo().size + val sizeFun = getName3(() => this.name) + + def getName4(foo: () => String): () => Int = () => foo().size // error + val fun4 = getName4(() => this.name) // error + fun4() // error + + def getName5(foo: () => String): () => () => Int = () => () => foo().size + val fun5a = getName5(() => this.name) + val fun5b = fun5a() + + def getName6(foo: () => String): () => () => Int = () => () => foo().size // error + val fun6a = getName6(() => this.name) // error + val fun6b = fun6a() + fun6b() // error + + val name = "hello" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function4.scala b/tests/neg-custom-args/safe-init/function4.scala new file mode 100644 index 000000000000..f35a537d00e4 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function4.scala @@ -0,0 +1,16 @@ +final class Foo { + def getSize(f: () => String): () => Int = () => f().size // error + + val f1 = getSize(() => this.name) // error + val f2 = getSize(() => "Jack") + + f2() + f1() // error + f2() + f1() // error + + val name = "hello" + + f1() // ok + f2() // ok +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function5.scala b/tests/neg-custom-args/safe-init/function5.scala new file mode 100644 index 000000000000..d00972566014 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function5.scala @@ -0,0 +1,19 @@ +final class Foo { + def getName1(f: () => String)(g: () => String): () => String = () => f() + g() // error + + val a1: () => String = () => this.name // error + val b1: () => String = () => "Jack" + val f1 = getName1(a1)(b1) + + f1() // error + + def getName2(f: () => String)(g: () => String): () => String = () => f() + g() + + val a2: () => String = () => this.name + val b2: () => String = () => "Jack" + val f2 = getName2(a2)(b2) + + val name = "hello" + + f1() // ok, inited +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function6.scala b/tests/neg-custom-args/safe-init/function6.scala new file mode 100644 index 000000000000..91545aad43b5 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function6.scala @@ -0,0 +1,33 @@ +final class Foo(x: Int) { + var title: String = _ // error + + def get(msg: String): () => String = () => { + title = "Mr." + title + " Jack" + ", " + msg + } + + val f = get("hello") + + if (x > 10) + f() + + println(title) // error +} + +final class Foo2(x: Int) { + var title: String = _ + + def get(msg: String): () => String = () => { + title = "Mr." + title + " Jack" + ", " + msg + } + + val f = get("hello") + + if (x > 10) + f() + else + f() + + println(title) +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function7.scala b/tests/neg-custom-args/safe-init/function7.scala new file mode 100644 index 000000000000..0a160e9f1602 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function7.scala @@ -0,0 +1,15 @@ +final class Foo(x: Int) { + var title: String = _ + + val f = + if (x > 10) + () => title = "hello" + else + () => println(title) // error + + f() // error + + println(title) // error + + title = "hello" +} diff --git a/tests/neg-custom-args/safe-init/function8.scala b/tests/neg-custom-args/safe-init/function8.scala new file mode 100644 index 000000000000..d8c4a6ccdede --- /dev/null +++ b/tests/neg-custom-args/safe-init/function8.scala @@ -0,0 +1,17 @@ +final class Foo(x: Int) { + var title: String = _ + + val f: () => Unit = + if (x > 10) + new Function0[Unit] { + def apply() = title = "hello" + } + else + () => println(title) // error + + f() // error + + println(title) // error + + title = "hello" +} diff --git a/tests/neg-custom-args/safe-init/function9.scala b/tests/neg-custom-args/safe-init/function9.scala new file mode 100644 index 000000000000..b8c06d4b9935 --- /dev/null +++ b/tests/neg-custom-args/safe-init/function9.scala @@ -0,0 +1,20 @@ +final class Foo(x: Int) { + var title: String = _ + + val f: () => Unit = + if (x > 10) + new Function0[Unit] { + def apply() = title = "hello" + } + else + () => println(title) // error + + val g: () => Unit = + if (x < 5) f else () => title = "hello" + + g() // error + + println(title) // error + + title = "hello" +} diff --git a/tests/neg-custom-args/safe-init/if.scala b/tests/neg-custom-args/safe-init/if.scala new file mode 100644 index 000000000000..b628031cc275 --- /dev/null +++ b/tests/neg-custom-args/safe-init/if.scala @@ -0,0 +1,44 @@ +class Foo(x: Int) { + var from: String = _ + var to: String = _ + val message = "hello, world" + + if (x > 5) + from = "Jack" + else + to = "Jim" + + from.size // error + to.size // error + + if (x > 5) { + from = "Jack" + to = "Jim" + } + else { + from = "Jack" + to = "Jim" + } + + from.size + to.size +} + + +class Bar(x: Int, m: Partial[String]) { + var from: String = _ // error + var to: String = _ + val message = "hello, world" + + if (x > 5) { + from = m + to = "Jim" + } + else { + from = "Jack" + to = "Jim" + } + + from.size // error + to.size +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner0.scala.bak b/tests/neg-custom-args/safe-init/inner0.scala.bak new file mode 100644 index 000000000000..cce9a6076766 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner0.scala.bak @@ -0,0 +1,9 @@ +class A { + val a = new A // error + 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/neg-custom-args/safe-init/inner1.scala b/tests/neg-custom-args/safe-init/inner1.scala new file mode 100644 index 000000000000..6dae6fdf1976 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner1.scala @@ -0,0 +1,26 @@ +import scala.annotation.filled + +class Foo { + val bar = new Bar(this) // error + new bar.Inner // error + + new this.Inner // error, as Inner access `this.list` + + val list = List(1, 2, 3) + + val inner: Inner @filled = new this.Inner // ok, `list` is instantiated + + val name = "good" + + class Inner { + val len = list.size // error: create new instance from line 5 + } +} + +class Bar(val foo: Partial[Foo]) { + val inner = new foo.Inner // error + + class Inner { + val len = inner.len + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner10.scala b/tests/neg-custom-args/safe-init/inner10.scala new file mode 100644 index 000000000000..effbd6f2b6be --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner10.scala @@ -0,0 +1,11 @@ +object Flags { + class Inner { + println(b) // error + + @scala.annotation.partial + def foo: Int = 5 + } + + new Flags.Inner // error + val b = 5 +} diff --git a/tests/neg-custom-args/safe-init/inner11.scala b/tests/neg-custom-args/safe-init/inner11.scala new file mode 100644 index 000000000000..3dd0f7435f53 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner11.scala @@ -0,0 +1,22 @@ +object NameKinds { + val a = b // error + + abstract class NameInfo + + abstract class NameKind(val tag: Int) { self => + type ThisInfo <: Info + + /** A simple info type; some subclasses of Kind define more refined versions */ + @scala.annotation.partial + class Info extends NameInfo { this: ThisInfo => + def kind = self + } + } + + abstract class ClassifiedNameKind(tag: Int, val infoString: String) extends NameKind(tag) { + type ThisInfo = Info + val info: Info @scala.annotation.filled = new Info + } + + val b = 0 +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner2.scala b/tests/neg-custom-args/safe-init/inner2.scala new file mode 100644 index 000000000000..a3dd5db821fd --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner2.scala @@ -0,0 +1,23 @@ +import scala.annotation.filled + +class Foo { + val list = List(1, 2, 3) + + class Inner { + val len = list.size + } + + val bar: Bar @filled = new Bar(this) + new bar.Inner // error +} + +import scala.annotation.partial + +class Bar(val foo: Partial[Foo]) { + val inner = new foo.Inner // error + + class Inner { + val x = new foo.Inner + val len = x.len + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner3.scala b/tests/neg-custom-args/safe-init/inner3.scala new file mode 100644 index 000000000000..6529e602580f --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner3.scala @@ -0,0 +1,21 @@ +class Foo { + val bar = new Bar(this) // error + var x: bar.Inner = _ // ok, partial value as type prefix + + class Inner { + val len = list.size + } + + val list = List(1, 2, 3) + + x = null +} + +class Bar(val foo: Partial[Foo]) { + val inner = new foo.Inner // error + + class Inner { + val x = new foo.Inner + val len = x.len + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner4.scala b/tests/neg-custom-args/safe-init/inner4.scala new file mode 100644 index 000000000000..3216644b6aed --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner4.scala @@ -0,0 +1,11 @@ +class Foo(val foo1: Partial[Foo], val foo2: Foo) { + class Inner { + val len = name.size // error + } + + new this.Inner // error + new foo1.Inner // error + new foo2.Inner + + val name = "hello" +} diff --git a/tests/neg-custom-args/safe-init/inner5.scala b/tests/neg-custom-args/safe-init/inner5.scala new file mode 100644 index 000000000000..894449cb8770 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner5.scala @@ -0,0 +1,8 @@ +trait Foo { + @scala.annotation.filled + class B { + foo(10) // error + } + + def foo(x: Int) = 5 + x +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner6.scala b/tests/neg-custom-args/safe-init/inner6.scala new file mode 100644 index 000000000000..742aee6cdd2c --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner6.scala @@ -0,0 +1,21 @@ +class Parent { + @scala.annotation.filled + class Inner1 { + val len = list.size + } + + class Inner2 { + val len = foo + } + + val list = List(3, 5, 6) + def foo: Int = 5 +} + +class Child extends Parent { + class InnerA extends Inner1 + class InnerB extends Inner2 // error + + new InnerA + new InnerB // error +} diff --git a/tests/neg-custom-args/safe-init/inner7.scala b/tests/neg-custom-args/safe-init/inner7.scala new file mode 100644 index 000000000000..7117c90feada --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner7.scala @@ -0,0 +1,15 @@ +class Parent { + @scala.annotation.filled + class Inner1 { + val len = foo // error + } + + val list = List(3, 5, 6) + def foo: Int = 5 +} + +class Child extends Parent { + class InnerA extends Inner1 + + new InnerA +} diff --git a/tests/neg-custom-args/safe-init/inner8.scala b/tests/neg-custom-args/safe-init/inner8.scala new file mode 100644 index 000000000000..28f7655859d5 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner8.scala @@ -0,0 +1,27 @@ +object Flags { + case class FlagSet(val bits: Long) { + def toTermFlags = if (bits == 0) this else { + println(size) // error + FlagSet(bits & ~KINDFLAGS | TERMS) + } + } + + private val flagName = Array.fill(64, 2)("") + + private final val TERMindex = 0 + private final val TYPEindex = 1 + private final val TERMS = 1 << TERMindex + private final val TYPES = 1 << TYPEindex + private final val KINDFLAGS = TERMS | TYPES + + private def commonFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + flagName(index)(TYPEindex) = name + FlagSet(TERMS | TYPES | (1L << index)) + } + + final val JavaStatic = commonFlag(31, "") + final val JavaStaticTerm = JavaStatic.toTermFlags // error + + final val size: Int = ??? +} diff --git a/tests/neg-custom-args/safe-init/inner9.scala b/tests/neg-custom-args/safe-init/inner9.scala new file mode 100644 index 000000000000..0e273f060784 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner9.scala @@ -0,0 +1,20 @@ +object Flags { + class Inner { + println(b) // error + } + + new Flags.Inner // error + + val a = this.b + 3 // error + val b = 5 +} + +object Flags2 { + class Inner { + println(b) + } + + + lazy val a = 3 + val b = 5 +} diff --git a/tests/neg-custom-args/safe-init/nested1.scala b/tests/neg-custom-args/safe-init/nested1.scala new file mode 100644 index 000000000000..94b97da783bd --- /dev/null +++ b/tests/neg-custom-args/safe-init/nested1.scala @@ -0,0 +1,26 @@ +class X { + object A { // error + name.size // error + def foo: Int = name.size // error + def bar: Int = 10 + } + + A.foo // error + A.bar // ok: forced + + val name = "jack" +} + + +class Y { + class A { + name.size // error + def foo: Int = name.size // error + def bar: Int = 10 + } + + (new A).foo // error + (new A).bar // error + + val name = "jack" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override1.scala b/tests/neg-custom-args/safe-init/override1.scala new file mode 100644 index 000000000000..ad8d1d3360d9 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override1.scala @@ -0,0 +1,17 @@ +import scala.annotation.partial + +trait Foo { + val x = "world" + foo(5) // error + + def foo(n: Int): String +} + + +trait Bar { + val x = "world" + foo(5) + + @partial + def foo(n: Int): String +} diff --git a/tests/neg-custom-args/safe-init/override2.scala b/tests/neg-custom-args/safe-init/override2.scala new file mode 100644 index 000000000000..0383034d919e --- /dev/null +++ b/tests/neg-custom-args/safe-init/override2.scala @@ -0,0 +1,20 @@ +import scala.annotation.partial + +trait Foo { + val x = "world" + foo(5) + + private def foo(n: Int): String = x + n +} + +class Bar extends Foo { + val y = "hello" + + foo(5) // error + + def foo(n: Int): String = { // need to be private or final + println("in foo") + println(y.size) + y + x + } +} diff --git a/tests/neg-custom-args/safe-init/override3.scala b/tests/neg-custom-args/safe-init/override3.scala new file mode 100644 index 000000000000..759c6937045c --- /dev/null +++ b/tests/neg-custom-args/safe-init/override3.scala @@ -0,0 +1,34 @@ +import scala.annotation.partial + +trait Foo { + println("init x") + val x = "world" + val y = foo(5) + + @partial + def foo(n: Int): String +} + +class Bar1 extends Foo { + val m = "hello" + + def foo(n: Int) = "world" // ok +} + +class Qux extends Bar1 { + val u = "hello" + + override def foo(n: Int) = u + "world" // error // error +} + +class Bar2 extends Foo { + val m = "hello" + + final def foo(n: Int) = "world" +} + +class Bar3 extends Foo { + val m = "hello" + + final def foo(n: Int) = m + "world" // error // error +} diff --git a/tests/neg-custom-args/safe-init/override4.scala b/tests/neg-custom-args/safe-init/override4.scala new file mode 100644 index 000000000000..326c15aab8a1 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override4.scala @@ -0,0 +1,38 @@ +import scala.annotation.partial +import scala.annotation.filled +import scala.collection.mutable + +class Foo { + val map: mutable.Map[Int, String] = mutable.Map.empty + + @filled + 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 + + override def enter(k: Int, v: String) = { // error + mymap(k) = v // error + } +} + +class Foo1 { + val map: mutable.Map[Int, String] = mutable.Map.empty + + @partial + def enter(k: Int, v: String) = map(k) = v // error // error +} + + +abstract class Foo2 { + def map: mutable.Map[Int, String] + + @partial + def enter(k: Int, v: String) = map(k) = v // error // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override5.scala b/tests/neg-custom-args/safe-init/override5.scala new file mode 100644 index 000000000000..692e06b2f11d --- /dev/null +++ b/tests/neg-custom-args/safe-init/override5.scala @@ -0,0 +1,36 @@ +trait Foo { + @scala.annotation.partial + def name: String + + val message = "hello, " + name +} + +class Bar extends Foo { + val name = "Jack" // error: partial cannot be implemented by val +} + + +trait Zen { + @scala.annotation.init + val name: String + + val message = "hello, " + name +} + +class Tao extends Zen { + val name = "Jack" // error: init cannot be implemented by val +} + + +trait Base { + @scala.annotation.init + val name: String + + val message = "hello, " + name +} + +class Derived(val name: String) extends Base + +class Derived2 extends Derived("hello") { + override val name: String = "ok" +} diff --git a/tests/neg-custom-args/safe-init/override6.scala b/tests/neg-custom-args/safe-init/override6.scala new file mode 100644 index 000000000000..6deefef2ce57 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override6.scala @@ -0,0 +1,8 @@ +trait Foo { + val name: String + val message = "hello, " + name // error: name should not be used during initialization +} + +class Bar extends Foo { + val name = "Jack" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala new file mode 100644 index 000000000000..712254754eba --- /dev/null +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -0,0 +1,41 @@ +import scala.annotation.partial + +trait Foo { + @partial + def getName: String + + @partial + def getTitle: String + + val message = "hello, " + getTitle + " " + getName +} + +class Bar(val name: String) extends Foo { + val title = "Mr." + + @partial + def getName = name // ok: name is a Param field + + @partial + def getTitle = title // error: title cannot use title // error +} + +object Test { + def main(args: Array[String]): Unit = { + new Bar("Jack") + } +} + +trait Dao(val name: String) extends Foo { + val title = "Mr." + + @partial + def getName = name // error: cannot access `name` // error +} + +trait Zen(val name: String) { + val title = "Mr." + + @partial + def getName = name // error: cannot access `name` // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override8.scala b/tests/neg-custom-args/safe-init/override8.scala new file mode 100644 index 000000000000..2646693904ed --- /dev/null +++ b/tests/neg-custom-args/safe-init/override8.scala @@ -0,0 +1,48 @@ +import scala.annotation.partial + +trait Foo { + val x = "world" + val y = foo(5) + + @partial + def foo(n: Int): String +} + +trait Bar { + val m = "hello" + + def foo(n: Int) = m + + def foo(x: String) = "hello, " + x +} + +class Qux extends Foo with Bar // error: Bar.foo needs to be annotated with `@partial` + +trait Yun { + val m = "hello" + + @partial + def foo(n: Int) = m // error // error +} + + +class Tao { + val m = "hello" + + def msg = "can be overriden" + + def foo(n: Int) = m + msg +} + +class Zen extends Tao with Foo // error: Tao.foo needs to be `@partial` + +class Lux { + val m = "hello" + + def msg = "can be overriden" + + @partial + def foo(n: Int) = m + msg // error // error // error +} + +class Logos extends Lux with Foo \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override9.scala b/tests/neg-custom-args/safe-init/override9.scala new file mode 100644 index 000000000000..5e429f632998 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override9.scala @@ -0,0 +1,9 @@ +trait Foo { + @scala.annotation.partial + def name: String + val message = "hello, " + name +} + +class Bar extends Foo { + def name = message // error // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent1.scala b/tests/neg-custom-args/safe-init/parent1.scala new file mode 100644 index 000000000000..459348eb5ada --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent1.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 // error + this.z // error + val m = this.x + + val y = "hello" + + def bar = y.size +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent2.scala b/tests/neg-custom-args/safe-init/parent2.scala new file mode 100644 index 000000000000..faa7c59ed2c6 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent2.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 // error + val m = this.x + this.z // error + + def bar = m.size + 6 +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent3.scala b/tests/neg-custom-args/safe-init/parent3.scala new file mode 100644 index 000000000000..513e73e8e740 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent3.scala @@ -0,0 +1,16 @@ +abstract class Parent(p: Partial[String]) { + val x = "name" + lazy val z = bar + def foo = bar + def bar: Int +} + +class Child(o: Partial[String]) extends Parent(o) { + val y = "hello" + + val m = this.x + this.foo // error + this.z // error + + def bar = y.size +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent4.scala b/tests/neg-custom-args/safe-init/parent4.scala new file mode 100644 index 000000000000..7a4d0485d2f9 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent4.scala @@ -0,0 +1,12 @@ +object Trees { + class ValDef { + def setMods(x: Int) = name.size // error + } + + class EmptyValDef extends ValDef { + setMods(5) // error + } + + val theEmptyValDef = new EmptyValDef // error + val name = "hello" +} diff --git a/tests/neg-custom-args/safe-init/parent5.scala b/tests/neg-custom-args/safe-init/parent5.scala new file mode 100644 index 000000000000..5a71f8437b1b --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent5.scala @@ -0,0 +1,21 @@ +import scala.collection.mutable + +class Foo { + val map: mutable.Map[Int, String] = mutable.Map.empty + + @scala.annotation.filled + def enter(k: Int, v: String) = map(k) = v + + def foo(x: Int) = 5 + x +} + +class Bar extends Foo { + enter(1, "one") + enter(2, "two") + + foo(4) // error + + val name = "bar" + + foo(4) // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent6.scala b/tests/neg-custom-args/safe-init/parent6.scala new file mode 100644 index 000000000000..3d8765cdae59 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent6.scala @@ -0,0 +1,21 @@ +import scala.annotation.filled + +trait Foo { + @filled + class A + + class B { + foo(10) + } + + def foo(x: Int) = 5 + x +} + + +class Bar extends Foo { + val a: A @filled = new A // OK + val b = new B // error + + override def foo(x: Int) = x + id + val id = 100 +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent7.scala b/tests/neg-custom-args/safe-init/parent7.scala new file mode 100644 index 000000000000..5c25b9ab1ae7 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent7.scala @@ -0,0 +1,12 @@ +trait Foo { + @scala.annotation.partial + def name: String + + def title: String +} + +trait Bar { this: Foo => + val message = "hello, " + name // ok: because `name` is marked partial + + println(title) // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent8.scala b/tests/neg-custom-args/safe-init/parent8.scala new file mode 100644 index 000000000000..21ed4794e1a1 --- /dev/null +++ b/tests/neg-custom-args/safe-init/parent8.scala @@ -0,0 +1,11 @@ +abstract class Foo { + val name: String = "Foo" + + def title: String +} + +trait Bar { this: Foo => + val message = "hello, " + name // ok: because `Foo` is a class + + println(title) // error +} diff --git a/tests/neg-custom-args/safe-init/partial-select1.scala b/tests/neg-custom-args/safe-init/partial-select1.scala new file mode 100644 index 000000000000..4913522030f3 --- /dev/null +++ b/tests/neg-custom-args/safe-init/partial-select1.scala @@ -0,0 +1,19 @@ +import scala.annotation.filled +class Parent { + val child: Child @filled = new Child(this) + child.number = 5 + + val name = "parent" +} + +class Child(parent: Partial[Parent]) { + val name = "child" + var number = 0 + + println(parent.name) // error + + def show = { + println(parent.name) + println(name) + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/partial-select2.scala b/tests/neg-custom-args/safe-init/partial-select2.scala new file mode 100644 index 000000000000..ebe0099e8fef --- /dev/null +++ b/tests/neg-custom-args/safe-init/partial-select2.scala @@ -0,0 +1,15 @@ +abstract class Parent { + var number = 0 + + def show: Unit +} + +class Child extends Parent { + number = 0 + + println(show) // error + + def show = println(name) // error + + val name = "child" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/partial-select3.scala b/tests/neg-custom-args/safe-init/partial-select3.scala new file mode 100644 index 000000000000..d908017bf769 --- /dev/null +++ b/tests/neg-custom-args/safe-init/partial-select3.scala @@ -0,0 +1,16 @@ +class Foo { + val name = "child" + + println(show) // error + + def show = println(name) +} + + +final class Bar { + val name = "child" + + println(show) + + def show = println(name) +} diff --git a/tests/neg-custom-args/safe-init/periods.scala b/tests/neg-custom-args/safe-init/periods.scala new file mode 100644 index 000000000000..0f8d3b83480b --- /dev/null +++ b/tests/neg-custom-args/safe-init/periods.scala @@ -0,0 +1,48 @@ +object Periods { + class Period(val code: Int) extends AnyVal + + object Period { + + /** The single-phase period consisting of given run id and phase id */ + def apply(rid: RunId, pid: PhaseId): Period = { + new Period(((rid << PhaseWidth) | pid) << PhaseWidth) + } + + /** The period consisting of given run id, and lo/hi phase ids */ + def apply(rid: RunId, loPid: PhaseId, hiPid: PhaseId): Period = { + new Period(((rid << PhaseWidth) | hiPid) << PhaseWidth | (hiPid - loPid)) + } + + /** The interval consisting of all periods of given run id */ + def allInRun(rid: RunId) = { + apply(rid, 0, PhaseMask) + } + } + + final val Nowhere = new Period(0) + + final val InitialPeriod = Period(InitialRunId, FirstPhaseId) + + final val InvalidPeriod = Period(NoRunId, NoPhaseId) + + /** An ordinal number for compiler runs. First run has number 1. */ + type RunId = Int + final val NoRunId = 0 + final val InitialRunId = 1 + final val RunWidth = java.lang.Integer.SIZE - PhaseWidth * 2 - 1/* sign */ + final val MaxPossibleRunId = (1 << RunWidth) - 1 + + /** An ordinal number for phases. First phase has number 1. */ + type PhaseId = Int + final val NoPhaseId = 0 + final val FirstPhaseId = 1 + + /** The number of bits needed to encode a phase identifier. */ + final val PhaseWidth = 7 + final val PhaseMask = (1 << PhaseWidth) - 1 + final val MaxPossiblePhaseId = PhaseMask + + + final val x = y // error + final val y: Int = 3 +} diff --git a/tests/neg-custom-args/safe-init/simple-1a.scala b/tests/neg-custom-args/safe-init/simple-1a.scala new file mode 100644 index 000000000000..44a11f4c9e59 --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1a.scala @@ -0,0 +1,4 @@ +class Foo { + val len = name.size // error + val name: String = "Jack" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1b.scala b/tests/neg-custom-args/safe-init/simple-1b.scala new file mode 100644 index 000000000000..1bc8b549e5da --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1b.scala @@ -0,0 +1,21 @@ +class Box(x: Int) { + val y = x + 1 + val z = f(5) // error + + List(3, 4, 5).map(_ * 2) + + var a = "hello" + + def f(m: Int) = m + a.size // error +} + +class Boite(x: Int) { + val y = x + 1 + val z = f(5) // error + + List(3, 4, 5).map(_ * 2) + + var a = "hello" + + final def f(m: Int) = m + a.size // error +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1c.scala b/tests/neg-custom-args/safe-init/simple-1c.scala new file mode 100644 index 000000000000..f946bdc00936 --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1c.scala @@ -0,0 +1,6 @@ +class Foo { + val list = List(4, 6) + val n = len + 5 // error + val len = list.size // standard typing: list is `unclassified`, this line invalid + // flow-sensitive typing + commit-only fields: allowed +} diff --git a/tests/neg-custom-args/safe-init/simple-1d.scala b/tests/neg-custom-args/safe-init/simple-1d.scala new file mode 100644 index 000000000000..9724cbb380fd --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1d.scala @@ -0,0 +1,11 @@ +class Foo { + val len = list.size // error + val list = List(4, 6) + + lazy val len2 = list2.size // ok + val list2 = List(4, 6) + + lazy val len3 = name.size // error: trigger from len4 + val len4 = len3 + 4 // error + val name = "hello" +} diff --git a/tests/neg-custom-args/safe-init/simple-1e.scala b/tests/neg-custom-args/safe-init/simple-1e.scala new file mode 100644 index 000000000000..f21058a6c1a7 --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1e.scala @@ -0,0 +1,13 @@ +class Foo { + def b = { + name.size // error + lazy val m = name.size // error: triggered from forcing `m` + def bar = name.size // error: triggered from calling `bar` + bar // error: trigger non-init + m // error: trigger + } + + b // error + + val name = "Jack" +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1f.scala b/tests/neg-custom-args/safe-init/simple-1f.scala new file mode 100644 index 000000000000..8206dc9aaeac --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1f.scala @@ -0,0 +1,22 @@ +class Foo(x: Partial[String]) { + var name: String = _ // error + name.size // error + + name = "hello, world" + name.size + + val y = name + y.size + + name = x +} + +class Bar(x: Partial[String]) { + var name: String = x // error + name.size // error + + name = "hello, world" + name.size + + name = x +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1g.scala b/tests/neg-custom-args/safe-init/simple-1g.scala new file mode 100644 index 000000000000..8ca8302de41d --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1g.scala @@ -0,0 +1,3 @@ +class Foo { + var name: String = _ // error: name is not initialized +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1h.scala b/tests/neg-custom-args/safe-init/simple-1h.scala new file mode 100644 index 000000000000..fec1eda08454 --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1h.scala @@ -0,0 +1,8 @@ +class Foo(n: Partial[String]) { + foo(new Foo("Jack")) // recursive creation + + val name: String = n // error + name.length // error + + private def foo(o: Foo) = o.name +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/simple-1i.scala b/tests/neg-custom-args/safe-init/simple-1i.scala new file mode 100644 index 000000000000..7f5e5e825b59 --- /dev/null +++ b/tests/neg-custom-args/safe-init/simple-1i.scala @@ -0,0 +1,11 @@ +class Foo(n: Partial[String]) { + foo(new Foo("Jack")) // recursive creation + + val name: String = n // error + name.length // error + + private def foo(o: Foo) = { + def bar = o.name + bar + } +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/tailrec.scala.bak b/tests/neg-custom-args/safe-init/tailrec.scala.bak new file mode 100644 index 000000000000..7522a424be4b --- /dev/null +++ b/tests/neg-custom-args/safe-init/tailrec.scala.bak @@ -0,0 +1,7 @@ +class Foo { + private def rec(x: Int): Int = + if (x == 0) 1 + else rec(x - 1) + + val n = rec(5) +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/unchecked1.scala b/tests/neg-custom-args/safe-init/unchecked1.scala new file mode 100644 index 000000000000..880d5160ca2c --- /dev/null +++ b/tests/neg-custom-args/safe-init/unchecked1.scala @@ -0,0 +1,36 @@ +abstract class Base { + @scala.annotation.partial + def foo: Int + @scala.annotation.partial + def bar: Int + + val a = foo +} + +class ChildA extends Base { + val b = 10 + + @unchecked + def foo = b + + def bar = b // error // error + + val c = this // error + @unchecked val d = this // ok +} + +@unchecked +class ChildB extends Base { + val b = 10 + + def foo = b + def bar = b +} + + +class ChildC extends Base { + val b = 10 + + def foo = b: @unchecked + def bar = b: @unchecked +} \ No newline at end of file diff --git a/tests/patmat/i3645e.check b/tests/patmat/i3645e.check index 14a7e9e29ab2..0b3718d9d364 100644 --- a/tests/patmat/i3645e.check +++ b/tests/patmat/i3645e.check @@ -1 +1 @@ -29: Pattern Match Exhaustivity: K1 +32: Pattern Match Exhaustivity: K1 diff --git a/tests/patmat/i3645e.scala b/tests/patmat/i3645e.scala index 7c4b13416712..8789b79dcdb6 100644 --- a/tests/patmat/i3645e.scala +++ b/tests/patmat/i3645e.scala @@ -1,8 +1,11 @@ +import scala.annotation.filled + object App { def main(args: Array[String]): Unit = { trait ModuleSig { type Upper + @filled trait FooSig { type Type <: Upper def subst[F[_]](fa: F[Int]): F[Type] diff --git a/tests/patmat/i3645f.check b/tests/patmat/i3645f.check index ee7113bc1140..bae54cc5b0d7 100644 --- a/tests/patmat/i3645f.check +++ b/tests/patmat/i3645f.check @@ -1 +1 @@ -30: Pattern Match Exhaustivity: K1 +33: Pattern Match Exhaustivity: K1 diff --git a/tests/patmat/i3645f.scala b/tests/patmat/i3645f.scala index 04d31aceea0f..ecf9f6f234bf 100644 --- a/tests/patmat/i3645f.scala +++ b/tests/patmat/i3645f.scala @@ -1,9 +1,12 @@ +import scala.annotation.filled + object App { def main(args: Array[String]): Unit = { trait ModuleSig { type U2 type U1 + @filled trait FooSig { type Type = (U1 & U2) def subst[F[_]](fa: F[Int]): F[Type] diff --git a/tests/patmat/i3645g.check b/tests/patmat/i3645g.check index 14a7e9e29ab2..0b3718d9d364 100644 --- a/tests/patmat/i3645g.check +++ b/tests/patmat/i3645g.check @@ -1 +1 @@ -29: Pattern Match Exhaustivity: K1 +32: Pattern Match Exhaustivity: K1 diff --git a/tests/patmat/i3645g.scala b/tests/patmat/i3645g.scala index f1c85b5d9e8c..8a573bc05538 100644 --- a/tests/patmat/i3645g.scala +++ b/tests/patmat/i3645g.scala @@ -1,9 +1,12 @@ +import scala.annotation.filled + object App { def main(args: Array[String]): Unit = { trait ModuleSig { type F[_] type U + @filled trait FooSig { type Type = F[U] def subst[F[_]](fa: F[Int]): F[Type] diff --git a/tests/patmat/i3938.scala b/tests/patmat/i3938.scala index d5c40b6490e5..7590e559ce86 100644 --- a/tests/patmat/i3938.scala +++ b/tests/patmat/i3938.scala @@ -10,7 +10,7 @@ */ -class Foo { +final class Foo { val bar = new Bar class Bar { sealed abstract class A From 8054290759a5c0c7d3a66b409cc84cd1314dc20d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 5 Oct 2018 09:52:04 +0200 Subject: [PATCH 02/83] Supress warnings for default values This is relaxed temporarily (should be reverted) to collect statistics about real annotations to source code. --- .../src/dotty/tools/dotc/transform/init/Analyzer.scala | 7 ++++--- compiler/src/dotty/tools/dotc/transform/init/Checker.scala | 6 +++--- compiler/src/dotty/tools/dotc/transform/init/Heap.scala | 6 +++--- tests/neg-custom-args/safe-init/Properties.scala | 3 ++- tests/neg-custom-args/safe-init/example.scala | 2 +- tests/neg-custom-args/safe-init/function6.scala | 2 +- tests/neg-custom-args/safe-init/simple-1g.scala | 3 ++- tests/neg-custom-args/safe-init/underscore.scala | 6 ++++++ 8 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/underscore.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 83825e1596c3..5bf6b58bf35f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -118,11 +118,12 @@ class Analyzer extends Indexer { analyzer => } def checkValDef(vdef: ValDef, env: Env)(implicit ctx: Context): Res = { - val rhsRes = apply(vdef.rhs, env) + val rhsRes = + if (tpd.isWildcardArg(vdef.rhs)) Res(value = NoValue) + else apply(vdef.rhs, env) val sym = vdef.symbol - // take `_` as uninitialized, otherwise it's initialized - if (!tpd.isWildcardArg(vdef.rhs)) sym.termRef match { + sym.termRef match { case tp @ TermRef(NoPrefix, _) => env.assign(tp.symbol, rhsRes.value, vdef.rhs.pos) case tp @ TermRef(prefix, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 04325d0a404a..d849d151bf5a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -139,9 +139,9 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val slice = root.heap(sliceValue.id).asSlice res.effects.foreach(_.report) - slice.notAssigned.foreach { sym => - if (!sym.is(Deferred)) ctx.warning(s"field ${sym.name} is not initialized", sym.pos) - } + // slice.notAssigned.foreach { sym => + // if (!sym.is(Deferred)) ctx.warning(s"field ${sym.name} is not initialized", sym.pos) + // } // filled check: try commit early if (obj.open || slice.widen != FullValue) filledCheck(obj, tmpl, root.heap) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 91511ef26d7b..aadd28236f2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -210,8 +210,8 @@ class Env(outerId: Int) extends HeapEntry { } else { var effs = Vector.empty[Effect] - if (value == NoValue) Res(effects = effs :+ Uninit(sym, pos)) - else Res(value = value) + assert(value != NoValue) + Res(value = value) } } else if (sym.isClass && this.containsClass(sym.asClass)) Res() @@ -310,7 +310,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo def isPartialOrFilled(value: Value): Boolean = value == PartialValue || value == FilledValue - if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) PartialValue + if (symbols.exists { case (sym, value) => sym.isField && !sym.is(Flags.PrivateOrLocal) && value == NoValue }) PartialValue else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isPartial || sym.info.isFilled) }) FilledValue else { // check outer diff --git a/tests/neg-custom-args/safe-init/Properties.scala b/tests/neg-custom-args/safe-init/Properties.scala index 526a9dd83cfe..edd9800db02c 100644 --- a/tests/neg-custom-args/safe-init/Properties.scala +++ b/tests/neg-custom-args/safe-init/Properties.scala @@ -98,5 +98,6 @@ private[scala] trait PropertiesTrait { val versionString = "version " + scalaPropOrElse("version.number", "(unknown)") val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2017, LAMP/EPFL and Lightbend, Inc.") - var x: Int = _ // error + x + 5 // error + var x: Int = 5 } diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala index 64ce9ed7a099..db0bb93078bb 100644 --- a/tests/neg-custom-args/safe-init/example.scala +++ b/tests/neg-custom-args/safe-init/example.scala @@ -2,7 +2,7 @@ import scala.annotation.filled class Parent(x: Int) { var name: String = _ - var addr: String = _ // error: addr is not initialized + var addr: String = _ val len = name.size // error: name not initialized lazy val l1 = name.size // ok: l1 is lazy diff --git a/tests/neg-custom-args/safe-init/function6.scala b/tests/neg-custom-args/safe-init/function6.scala index 91545aad43b5..ab12d8ce5f3a 100644 --- a/tests/neg-custom-args/safe-init/function6.scala +++ b/tests/neg-custom-args/safe-init/function6.scala @@ -1,5 +1,5 @@ final class Foo(x: Int) { - var title: String = _ // error + var title: String = _ def get(msg: String): () => String = () => { title = "Mr." diff --git a/tests/neg-custom-args/safe-init/simple-1g.scala b/tests/neg-custom-args/safe-init/simple-1g.scala index 8ca8302de41d..66c853e0cbf4 100644 --- a/tests/neg-custom-args/safe-init/simple-1g.scala +++ b/tests/neg-custom-args/safe-init/simple-1g.scala @@ -1,3 +1,4 @@ class Foo { - var name: String = _ // error: name is not initialized + var name: String = _ + println(name) // error: name is not initialized } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/underscore.scala b/tests/neg-custom-args/safe-init/underscore.scala new file mode 100644 index 000000000000..d74ed523fd9a --- /dev/null +++ b/tests/neg-custom-args/safe-init/underscore.scala @@ -0,0 +1,6 @@ +class Foo { + val a: Int = c // error + var b: String = _ + var c: Int = _ + 5 + c // error +} \ No newline at end of file From f3242c7911d820a9f219979374d2658e9665e7fd Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 5 Oct 2018 16:49:17 +0200 Subject: [PATCH 03/83] Support super --- .../tools/dotc/transform/init/Analyzer.scala | 13 ++++++---- .../tools/dotc/transform/init/Values.scala | 26 +++++++++---------- tests/neg-custom-args/safe-init/super.scala | 12 +++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/super.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 5bf6b58bf35f..37f149090885 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -88,9 +88,6 @@ class Analyzer extends Indexer { analyzer => assert(cls.is(Flags.Module) && !enclosedIn(ctx.owner, cls)) Res() } - case tp @ SuperType(thistpe, supertpe) => - // TODO : handle `supertpe` - checkRef(thistpe, env, pos) }) def checkClosure(sym: Symbol, tree: Tree, env: Env)(implicit ctx: Context): Res = { @@ -263,6 +260,12 @@ class Analyzer extends Indexer { analyzer => } } + def checkSuper(tree: Tree, supert: Super, env: Env)(implicit ctx: Context): Res = { + val SuperType(thistpe, supertpe) = supert.tpe + val thisRef = checkRef(thistpe, env, tree.pos) + thisRef.value.select(tree.symbol, env.heap, tree.pos, isSuper = true) + } + object NewEx { def extract(tp: Type)(implicit ctx: Context): TypeRef = tp.dealias match { case tref: TypeRef => tref @@ -290,8 +293,8 @@ class Analyzer extends Indexer { analyzer => checkRef(tree.tpe, env, tree.pos) case tree: This => checkRef(tree.tpe, env, tree.pos) - case tree: Super => - checkRef(tree.tpe, env, tree.pos) + case tree @ Select(supert: Super, _) => + checkSuper(tree, supert, env) case tree: Select if tree.symbol.isTerm => checkSelect(tree, env) case tree: If => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 0b00870ccb89..d12aaab55013 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -46,7 +46,7 @@ object Value { /** Abstract values in analysis */ sealed trait Value { /** Select a member on a value */ - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean = false)(implicit ctx: Context): Res /** Assign on a value */ def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res @@ -124,7 +124,7 @@ sealed trait Value { /** The value is absent */ object NoValue extends Value { def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = ??? def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? @@ -142,9 +142,9 @@ case class UnionValue(val values: Set[SingleValue]) extends Value { } } - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { values.foldLeft(Res()) { (acc, value) => - value.select(sym, heap, pos).join(acc) + value.select(sym, heap, pos, isSuper).join(acc) } } @@ -187,7 +187,7 @@ abstract sealed class OpaqueValue extends SingleValue { } object FullValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) else Res() @@ -214,7 +214,7 @@ object FullValue extends OpaqueValue { } object PartialValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) @@ -266,7 +266,7 @@ object PartialValue extends OpaqueValue { } object FilledValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { val res = Res() if (sym.is(Flags.Method)) { if (!sym.isPartial && !sym.isFilled && !sym.name.is(DefaultGetterName)) @@ -318,7 +318,7 @@ object FilledValue extends OpaqueValue { abstract class FunctionValue extends SingleValue { self => def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = sym.name match { + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = sym.name match { case nme.apply | nme.lift => Res(value = this) case nme.compose => val selectedFun = new FunctionValue() { @@ -429,7 +429,7 @@ abstract class FunctionValue extends SingleValue { self => /** A lazy value */ abstract class LazyValue extends SingleValue { // not supported - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = ??? def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? @@ -443,7 +443,7 @@ class SliceValue(val id: Int) extends SingleValue { /** not supported, impossible to apply an object value */ def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { val slice = heap(id).asSlice val value = slice(sym) @@ -524,8 +524,8 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { /** not supported, impossible to apply an object value */ def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position)(implicit ctx: Context): Res = { - val target = resolve(sym) + def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { + val target = if (isSuper) sym else resolve(sym) // select on self type if (!target.exists) { @@ -542,7 +542,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val res = slices(cls).select(target, heap, pos) // ignore field access, but field access in Scala // are method calls, thus is unsafe as well - if (open && target.is(Flags.Method, butNot = Flags.Lazy) && + if (!isSuper && open && target.is(Flags.Method, butNot = Flags.Lazy) && !target.isPartial && !target.isFilled && !target.isOverride && diff --git a/tests/neg-custom-args/safe-init/super.scala b/tests/neg-custom-args/safe-init/super.scala new file mode 100644 index 000000000000..7d3780fe0722 --- /dev/null +++ b/tests/neg-custom-args/safe-init/super.scala @@ -0,0 +1,12 @@ +class Base { + val x = 10 + @scala.annotation.filled + def foo: Int = x +} + +class Child extends Base { + private def foo: Int = y // error + + val y = foo // error + val z = super.foo +} \ No newline at end of file From 2b626a826b3da3d9270a8b51c38e07587a61f759 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 8 Oct 2018 13:55:41 +0200 Subject: [PATCH 04/83] Tweak check conditions --- compiler/src/dotty/tools/dotc/transform/init/Checker.scala | 6 ++++-- compiler/src/dotty/tools/dotc/transform/init/Heap.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index d849d151bf5a..250aaaf524a5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -144,7 +144,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => // } // filled check: try commit early - if (obj.open || slice.widen != FullValue) filledCheck(obj, tmpl, root.heap) + if (obj.open) filledCheck(obj, tmpl, root.heap) } def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { @@ -240,6 +240,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } def checkValDef(sym: Symbol): Unit = { + if (sym.is(Flags.PrivateOrLocal) || sym.hasAnnotation(defn.UncheckedAnnot)) return + val isOverride = sym.allOverriddenSymbols.exists(sym => sym.isInit) val expected: OpaqueValue = if (isOverride) FullValue @@ -254,7 +256,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => checkMethod(ddef.symbol) case vdef: ValDef if vdef.symbol.is(Lazy) => checkLazy(vdef.symbol) - case vdef: ValDef if !vdef.symbol.hasAnnotation(defn.UncheckedAnnot) => + case vdef: ValDef => checkValDef(vdef.symbol) case _ => } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index aadd28236f2f..52bfc32cddd1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -310,7 +310,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo def isPartialOrFilled(value: Value): Boolean = value == PartialValue || value == FilledValue - if (symbols.exists { case (sym, value) => sym.isField && !sym.is(Flags.PrivateOrLocal) && value == NoValue }) PartialValue + if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) PartialValue else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isPartial || sym.info.isFilled) }) FilledValue else { // check outer From 9378a9f0076c979f3ab3e6eeded49d230cccd8eb Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 18 Oct 2018 11:40:49 +0200 Subject: [PATCH 05/83] Prepare tests for V3 --- .../safe-init/Properties.scala | 5 ++-- tests/neg-custom-args/safe-init/cycle.scala | 4 +-- .../safe-init/double-list2.scala | 4 +-- tests/neg-custom-args/safe-init/example.scala | 6 ++--- .../neg-custom-args/safe-init/function2.scala | 6 ++--- .../neg-custom-args/safe-init/function3.scala | 2 +- .../neg-custom-args/safe-init/function4.scala | 2 +- .../neg-custom-args/safe-init/function5.scala | 2 +- .../neg-custom-args/safe-init/function6.scala | 4 +-- .../neg-custom-args/safe-init/function8.scala | 2 +- tests/neg-custom-args/safe-init/if.scala | 2 +- tests/neg-custom-args/safe-init/inner1.scala | 9 ++++--- tests/neg-custom-args/safe-init/inner10.scala | 3 --- tests/neg-custom-args/safe-init/inner11.scala | 27 ++++++++++++++----- tests/neg-custom-args/safe-init/inner2.scala | 6 +---- tests/neg-custom-args/safe-init/inner3.scala | 2 +- tests/neg-custom-args/safe-init/inner5.scala | 2 +- tests/neg-custom-args/safe-init/inner6.scala | 2 +- tests/neg-custom-args/safe-init/inner7.scala | 2 +- .../neg-custom-args/safe-init/override1.scala | 19 +++++++------ .../neg-custom-args/safe-init/override2.scala | 9 ++++--- .../neg-custom-args/safe-init/override4.scala | 4 +-- .../neg-custom-args/safe-init/override5.scala | 3 --- .../neg-custom-args/safe-init/override6.scala | 4 +-- .../neg-custom-args/safe-init/override7.scala | 11 ++------ .../neg-custom-args/safe-init/override8.scala | 3 +-- .../neg-custom-args/safe-init/override9.scala | 1 - tests/neg-custom-args/safe-init/parent5.scala | 2 +- tests/neg-custom-args/safe-init/parent6.scala | 8 +++--- .../safe-init/partial-select1.scala | 3 +-- .../safe-init/partial-select3.scala | 10 +++---- .../neg-custom-args/safe-init/simple-1b.scala | 11 -------- tests/neg-custom-args/safe-init/super.scala | 2 +- .../safe-init/unchecked1.scala | 7 ++--- 34 files changed, 84 insertions(+), 105 deletions(-) diff --git a/tests/neg-custom-args/safe-init/Properties.scala b/tests/neg-custom-args/safe-init/Properties.scala index edd9800db02c..52131a426da2 100644 --- a/tests/neg-custom-args/safe-init/Properties.scala +++ b/tests/neg-custom-args/safe-init/Properties.scala @@ -12,11 +12,10 @@ package util import java.io.{ IOException, PrintWriter } import java.util.jar.Attributes.{ Name => AttributeName } -import scala.annotation.partial private[scala] trait PropertiesTrait { - @partial protected def propCategory: String // specializes the remainder of the values - @partial protected def pickJarBasedOn: Class[_] // props file comes from jar containing this + 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" diff --git a/tests/neg-custom-args/safe-init/cycle.scala b/tests/neg-custom-args/safe-init/cycle.scala index 87b31cf5bd9d..8c84df26afb2 100644 --- a/tests/neg-custom-args/safe-init/cycle.scala +++ b/tests/neg-custom-args/safe-init/cycle.scala @@ -1,7 +1,5 @@ -import scala.annotation.filled - class Parent { - val child: Child @filled = new Child(this) + val child: Child = new Child(this) child.show // error val name = "parent" diff --git a/tests/neg-custom-args/safe-init/double-list2.scala b/tests/neg-custom-args/safe-init/double-list2.scala index a6497bd6007f..77c63c406674 100644 --- a/tests/neg-custom-args/safe-init/double-list2.scala +++ b/tests/neg-custom-args/safe-init/double-list2.scala @@ -1,7 +1,5 @@ -import scala.annotation.filled - class List { - val sentinel: Node @filled = new Node(null, null, this, null) + val sentinel: Node = new Node(null, null, this, null) def insert(data: AnyRef) = sentinel.insertAfter(data) } diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala index db0bb93078bb..c2e5795b9c2a 100644 --- a/tests/neg-custom-args/safe-init/example.scala +++ b/tests/neg-custom-args/safe-init/example.scala @@ -1,5 +1,3 @@ -import scala.annotation.filled - class Parent(x: Int) { var name: String = _ var addr: String = _ @@ -9,8 +7,8 @@ class Parent(x: Int) { lazy val l2 = addr.size // error: l2 is forced at L32 before `addr` is initialized val fun: Int => Int = n => n + list.size // ok, fun is a partial value - val bar: Bar @ filled = new Bar("bar", fun) // ok, Bar accepts partial value - bar.result // error: bar is a partial value + val bar: Bar = new Bar("bar", fun) // ok, Bar accepts partial value + bar.result // error: bar depends on list val child = new Child(this) // error: `this` is partial, while full value expected List(5, 9).map(n => n + list.size) // error: partial value used as full value diff --git a/tests/neg-custom-args/safe-init/function2.scala b/tests/neg-custom-args/safe-init/function2.scala index e7091284efc8..ef50c123d7f9 100644 --- a/tests/neg-custom-args/safe-init/function2.scala +++ b/tests/neg-custom-args/safe-init/function2.scala @@ -1,11 +1,11 @@ class Foo { - private def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below + def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below fun(5) // error: select on partial value - private def getThis: Foo = this + def getThis: Foo = this getThis.x // error - private def getThis(x: Int): Foo = this + def getThis(x: Int): Foo = this getThis(56).x // error val x = "hello" diff --git a/tests/neg-custom-args/safe-init/function3.scala b/tests/neg-custom-args/safe-init/function3.scala index 0275c764e1d2..31a957072993 100644 --- a/tests/neg-custom-args/safe-init/function3.scala +++ b/tests/neg-custom-args/safe-init/function3.scala @@ -1,4 +1,4 @@ -final class Foo { +class Foo { def getName1(foo: Foo): String = foo.name // error getName1(this) // error diff --git a/tests/neg-custom-args/safe-init/function4.scala b/tests/neg-custom-args/safe-init/function4.scala index f35a537d00e4..a29edcda47cf 100644 --- a/tests/neg-custom-args/safe-init/function4.scala +++ b/tests/neg-custom-args/safe-init/function4.scala @@ -1,4 +1,4 @@ -final class Foo { +class Foo { def getSize(f: () => String): () => Int = () => f().size // error val f1 = getSize(() => this.name) // error diff --git a/tests/neg-custom-args/safe-init/function5.scala b/tests/neg-custom-args/safe-init/function5.scala index d00972566014..bc9b682255a5 100644 --- a/tests/neg-custom-args/safe-init/function5.scala +++ b/tests/neg-custom-args/safe-init/function5.scala @@ -1,4 +1,4 @@ -final class Foo { +class Foo { def getName1(f: () => String)(g: () => String): () => String = () => f() + g() // error val a1: () => String = () => this.name // error diff --git a/tests/neg-custom-args/safe-init/function6.scala b/tests/neg-custom-args/safe-init/function6.scala index ab12d8ce5f3a..861a18ad2e3c 100644 --- a/tests/neg-custom-args/safe-init/function6.scala +++ b/tests/neg-custom-args/safe-init/function6.scala @@ -1,4 +1,4 @@ -final class Foo(x: Int) { +class Foo(x: Int) { var title: String = _ def get(msg: String): () => String = () => { @@ -14,7 +14,7 @@ final class Foo(x: Int) { println(title) // error } -final class Foo2(x: Int) { +class Foo2(x: Int) { var title: String = _ def get(msg: String): () => String = () => { diff --git a/tests/neg-custom-args/safe-init/function8.scala b/tests/neg-custom-args/safe-init/function8.scala index d8c4a6ccdede..45cb4b9f158e 100644 --- a/tests/neg-custom-args/safe-init/function8.scala +++ b/tests/neg-custom-args/safe-init/function8.scala @@ -1,4 +1,4 @@ -final class Foo(x: Int) { +class Foo(x: Int) { var title: String = _ val f: () => Unit = diff --git a/tests/neg-custom-args/safe-init/if.scala b/tests/neg-custom-args/safe-init/if.scala index b628031cc275..2463d9a1d3a2 100644 --- a/tests/neg-custom-args/safe-init/if.scala +++ b/tests/neg-custom-args/safe-init/if.scala @@ -26,7 +26,7 @@ class Foo(x: Int) { class Bar(x: Int, m: Partial[String]) { - var from: String = _ // error + var from: String = _ var to: String = _ val message = "hello, world" diff --git a/tests/neg-custom-args/safe-init/inner1.scala b/tests/neg-custom-args/safe-init/inner1.scala index 6dae6fdf1976..7fb35736a6f9 100644 --- a/tests/neg-custom-args/safe-init/inner1.scala +++ b/tests/neg-custom-args/safe-init/inner1.scala @@ -1,5 +1,3 @@ -import scala.annotation.filled - class Foo { val bar = new Bar(this) // error new bar.Inner // error @@ -8,7 +6,8 @@ class Foo { val list = List(1, 2, 3) - val inner: Inner @filled = new this.Inner // ok, `list` is instantiated + val inner: Inner = new this.Inner // ok, `list` is instantiated + lib.escape(inner) // ok, `inner` is fully initialized val name = "good" @@ -17,6 +16,10 @@ class Foo { } } +object lib { + def escape(x: Foo#Inner): Unit = ??? +} + class Bar(val foo: Partial[Foo]) { val inner = new foo.Inner // error diff --git a/tests/neg-custom-args/safe-init/inner10.scala b/tests/neg-custom-args/safe-init/inner10.scala index effbd6f2b6be..9a25032cbae8 100644 --- a/tests/neg-custom-args/safe-init/inner10.scala +++ b/tests/neg-custom-args/safe-init/inner10.scala @@ -1,9 +1,6 @@ object Flags { class Inner { println(b) // error - - @scala.annotation.partial - def foo: Int = 5 } new Flags.Inner // error diff --git a/tests/neg-custom-args/safe-init/inner11.scala b/tests/neg-custom-args/safe-init/inner11.scala index 3dd0f7435f53..979f66530690 100644 --- a/tests/neg-custom-args/safe-init/inner11.scala +++ b/tests/neg-custom-args/safe-init/inner11.scala @@ -1,13 +1,10 @@ object NameKinds { - val a = b // error - abstract class NameInfo abstract class NameKind(val tag: Int) { self => type ThisInfo <: Info - /** A simple info type; some subclasses of Kind define more refined versions */ - @scala.annotation.partial + @scala.annotation.init class Info extends NameInfo { this: ThisInfo => def kind = self } @@ -15,8 +12,26 @@ object NameKinds { abstract class ClassifiedNameKind(tag: Int, val infoString: String) extends NameKind(tag) { type ThisInfo = Info - val info: Info @scala.annotation.filled = new Info + val info: Info = new Info + info.kind // error + } +} + +object NameKinds { + abstract class NameInfo + + abstract class NameKind(val tag: Int) { self => + type ThisInfo <: Info + + @scala.annotation.init + class Info extends NameInfo { this: ThisInfo => + def kind = "info" + } } - val b = 0 + abstract class ClassifiedNameKind(tag: Int, val infoString: String) extends NameKind(tag) { + type ThisInfo = Info + val info: Info = new Info + info.kind // ok + } } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/inner2.scala b/tests/neg-custom-args/safe-init/inner2.scala index a3dd5db821fd..fab717df64ea 100644 --- a/tests/neg-custom-args/safe-init/inner2.scala +++ b/tests/neg-custom-args/safe-init/inner2.scala @@ -1,5 +1,3 @@ -import scala.annotation.filled - class Foo { val list = List(1, 2, 3) @@ -7,12 +5,10 @@ class Foo { val len = list.size } - val bar: Bar @filled = new Bar(this) + val bar: Bar = new Bar(this) new bar.Inner // error } -import scala.annotation.partial - class Bar(val foo: Partial[Foo]) { val inner = new foo.Inner // error diff --git a/tests/neg-custom-args/safe-init/inner3.scala b/tests/neg-custom-args/safe-init/inner3.scala index 6529e602580f..d7925979346a 100644 --- a/tests/neg-custom-args/safe-init/inner3.scala +++ b/tests/neg-custom-args/safe-init/inner3.scala @@ -1,5 +1,5 @@ class Foo { - val bar = new Bar(this) // error + val bar = new Bar(this) var x: bar.Inner = _ // ok, partial value as type prefix class Inner { diff --git a/tests/neg-custom-args/safe-init/inner5.scala b/tests/neg-custom-args/safe-init/inner5.scala index 894449cb8770..746be3cb367e 100644 --- a/tests/neg-custom-args/safe-init/inner5.scala +++ b/tests/neg-custom-args/safe-init/inner5.scala @@ -1,5 +1,5 @@ trait Foo { - @scala.annotation.filled + @scala.annotation.init class B { foo(10) // error } diff --git a/tests/neg-custom-args/safe-init/inner6.scala b/tests/neg-custom-args/safe-init/inner6.scala index 742aee6cdd2c..2912a205ea1b 100644 --- a/tests/neg-custom-args/safe-init/inner6.scala +++ b/tests/neg-custom-args/safe-init/inner6.scala @@ -1,5 +1,5 @@ class Parent { - @scala.annotation.filled + @scala.annotation.init class Inner1 { val len = list.size } diff --git a/tests/neg-custom-args/safe-init/inner7.scala b/tests/neg-custom-args/safe-init/inner7.scala index 7117c90feada..46ecd21cc278 100644 --- a/tests/neg-custom-args/safe-init/inner7.scala +++ b/tests/neg-custom-args/safe-init/inner7.scala @@ -1,5 +1,5 @@ class Parent { - @scala.annotation.filled + @scala.annotation.init class Inner1 { val len = foo // error } diff --git a/tests/neg-custom-args/safe-init/override1.scala b/tests/neg-custom-args/safe-init/override1.scala index ad8d1d3360d9..1c57aa848011 100644 --- a/tests/neg-custom-args/safe-init/override1.scala +++ b/tests/neg-custom-args/safe-init/override1.scala @@ -1,17 +1,20 @@ -import scala.annotation.partial - trait Foo { val x = "world" - foo(5) // error + foo(5) // ok def foo(n: Int): String } -trait Bar { - val x = "world" - foo(5) +abstract class Bar extends Foo { + foo(5) // ok +} - @partial - def foo(n: Int): String +class Qux(x: Int) extends Bar { + def foo(n: Int) = x + n // ok +} + +class Yun extends Bar { + val x: Int = 10 + def foo(n: Int) = x + n // error } diff --git a/tests/neg-custom-args/safe-init/override2.scala b/tests/neg-custom-args/safe-init/override2.scala index 0383034d919e..f5b71e8cf057 100644 --- a/tests/neg-custom-args/safe-init/override2.scala +++ b/tests/neg-custom-args/safe-init/override2.scala @@ -1,16 +1,17 @@ -import scala.annotation.partial - trait Foo { val x = "world" foo(5) + def bar(x: Int): Int + private def foo(n: Int): String = x + n } -class Bar extends Foo { +abstract class Bar extends Foo { val y = "hello" - foo(5) // error + foo(5) + bar(10) // error def foo(n: Int): String = { // need to be private or final println("in foo") diff --git a/tests/neg-custom-args/safe-init/override4.scala b/tests/neg-custom-args/safe-init/override4.scala index 326c15aab8a1..f52dde03ac55 100644 --- a/tests/neg-custom-args/safe-init/override4.scala +++ b/tests/neg-custom-args/safe-init/override4.scala @@ -1,11 +1,11 @@ import scala.annotation.partial -import scala.annotation.filled +import scala.annotation.init import scala.collection.mutable class Foo { val map: mutable.Map[Int, String] = mutable.Map.empty - @filled + @init def enter(k: Int, v: String) = map(k) = v } diff --git a/tests/neg-custom-args/safe-init/override5.scala b/tests/neg-custom-args/safe-init/override5.scala index 692e06b2f11d..cc3410784c88 100644 --- a/tests/neg-custom-args/safe-init/override5.scala +++ b/tests/neg-custom-args/safe-init/override5.scala @@ -1,5 +1,4 @@ trait Foo { - @scala.annotation.partial def name: String val message = "hello, " + name @@ -11,7 +10,6 @@ class Bar extends Foo { trait Zen { - @scala.annotation.init val name: String val message = "hello, " + name @@ -23,7 +21,6 @@ class Tao extends Zen { trait Base { - @scala.annotation.init val name: String val message = "hello, " + name diff --git a/tests/neg-custom-args/safe-init/override6.scala b/tests/neg-custom-args/safe-init/override6.scala index 6deefef2ce57..0c04ea91300d 100644 --- a/tests/neg-custom-args/safe-init/override6.scala +++ b/tests/neg-custom-args/safe-init/override6.scala @@ -1,8 +1,8 @@ trait Foo { val name: String - val message = "hello, " + name // error: name should not be used during initialization + val message = "hello, " + name } class Bar extends Foo { - val name = "Jack" + val name = "Jack" // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala index 712254754eba..62b97f9c4af3 100644 --- a/tests/neg-custom-args/safe-init/override7.scala +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -1,10 +1,6 @@ -import scala.annotation.partial - trait Foo { - @partial def getName: String - @partial def getTitle: String val message = "hello, " + getTitle + " " + getName @@ -13,10 +9,8 @@ trait Foo { class Bar(val name: String) extends Foo { val title = "Mr." - @partial def getName = name // ok: name is a Param field - @partial def getTitle = title // error: title cannot use title // error } @@ -29,13 +23,12 @@ object Test { trait Dao(val name: String) extends Foo { val title = "Mr." - @partial - def getName = name // error: cannot access `name` // error + def getName = name } trait Zen(val name: String) { val title = "Mr." - @partial + @scala.annotation.partial def getName = name // error: cannot access `name` // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override8.scala b/tests/neg-custom-args/safe-init/override8.scala index 2646693904ed..d1435cbe618c 100644 --- a/tests/neg-custom-args/safe-init/override8.scala +++ b/tests/neg-custom-args/safe-init/override8.scala @@ -4,7 +4,6 @@ trait Foo { val x = "world" val y = foo(5) - @partial def foo(n: Int): String } @@ -34,7 +33,7 @@ class Tao { def foo(n: Int) = m + msg } -class Zen extends Tao with Foo // error: Tao.foo needs to be `@partial` +class Zen extends Tao with Foo // error: Tao.foo needs to be `@init` class Lux { val m = "hello" diff --git a/tests/neg-custom-args/safe-init/override9.scala b/tests/neg-custom-args/safe-init/override9.scala index 5e429f632998..514ee075a9ef 100644 --- a/tests/neg-custom-args/safe-init/override9.scala +++ b/tests/neg-custom-args/safe-init/override9.scala @@ -1,5 +1,4 @@ trait Foo { - @scala.annotation.partial def name: String val message = "hello, " + name } diff --git a/tests/neg-custom-args/safe-init/parent5.scala b/tests/neg-custom-args/safe-init/parent5.scala index 5a71f8437b1b..a61ff6194377 100644 --- a/tests/neg-custom-args/safe-init/parent5.scala +++ b/tests/neg-custom-args/safe-init/parent5.scala @@ -3,7 +3,7 @@ import scala.collection.mutable class Foo { val map: mutable.Map[Int, String] = mutable.Map.empty - @scala.annotation.filled + @scala.annotation.init def enter(k: Int, v: String) = map(k) = v def foo(x: Int) = 5 + x diff --git a/tests/neg-custom-args/safe-init/parent6.scala b/tests/neg-custom-args/safe-init/parent6.scala index 3d8765cdae59..a29455eb3f85 100644 --- a/tests/neg-custom-args/safe-init/parent6.scala +++ b/tests/neg-custom-args/safe-init/parent6.scala @@ -1,7 +1,4 @@ -import scala.annotation.filled - trait Foo { - @filled class A class B { @@ -13,8 +10,9 @@ trait Foo { class Bar extends Foo { - val a: A @filled = new A // OK - val b = new B // error + val a: A = new A // OK + println(a) // OK + val b = new B // error override def foo(x: Int) = x + id val id = 100 diff --git a/tests/neg-custom-args/safe-init/partial-select1.scala b/tests/neg-custom-args/safe-init/partial-select1.scala index 4913522030f3..dbf7c62d659a 100644 --- a/tests/neg-custom-args/safe-init/partial-select1.scala +++ b/tests/neg-custom-args/safe-init/partial-select1.scala @@ -1,6 +1,5 @@ -import scala.annotation.filled class Parent { - val child: Child @filled = new Child(this) + val child: Child = new Child(this) child.number = 5 val name = "parent" diff --git a/tests/neg-custom-args/safe-init/partial-select3.scala b/tests/neg-custom-args/safe-init/partial-select3.scala index d908017bf769..dabf3b749dca 100644 --- a/tests/neg-custom-args/safe-init/partial-select3.scala +++ b/tests/neg-custom-args/safe-init/partial-select3.scala @@ -1,16 +1,16 @@ class Foo { val name = "child" - println(show) // error + println(show) def show = println(name) } -final class Bar { - val name = "child" +class Bar { + println(show) // error - println(show) + def show = println(name) // error - def show = println(name) + val name = "child" } diff --git a/tests/neg-custom-args/safe-init/simple-1b.scala b/tests/neg-custom-args/safe-init/simple-1b.scala index 1bc8b549e5da..22268cdf3ee1 100644 --- a/tests/neg-custom-args/safe-init/simple-1b.scala +++ b/tests/neg-custom-args/safe-init/simple-1b.scala @@ -8,14 +8,3 @@ class Box(x: Int) { def f(m: Int) = m + a.size // error } - -class Boite(x: Int) { - val y = x + 1 - val z = f(5) // error - - List(3, 4, 5).map(_ * 2) - - var a = "hello" - - final def f(m: Int) = m + a.size // error -} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/super.scala b/tests/neg-custom-args/safe-init/super.scala index 7d3780fe0722..fe24367a606a 100644 --- a/tests/neg-custom-args/safe-init/super.scala +++ b/tests/neg-custom-args/safe-init/super.scala @@ -1,6 +1,6 @@ class Base { val x = 10 - @scala.annotation.filled + @scala.annotation.init def foo: Int = x } diff --git a/tests/neg-custom-args/safe-init/unchecked1.scala b/tests/neg-custom-args/safe-init/unchecked1.scala index 880d5160ca2c..794629455054 100644 --- a/tests/neg-custom-args/safe-init/unchecked1.scala +++ b/tests/neg-custom-args/safe-init/unchecked1.scala @@ -1,10 +1,8 @@ abstract class Base { - @scala.annotation.partial def foo: Int - @scala.annotation.partial def bar: Int - val a = foo + val a = foo + bar } class ChildA extends Base { @@ -15,8 +13,7 @@ class ChildA extends Base { def bar = b // error // error - val c = this // error - @unchecked val d = this // ok + val c = this } @unchecked From a92aaafadef9dc41abffa22593331887e87cf0a7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 18 Oct 2018 17:42:18 +0200 Subject: [PATCH 06/83] Support call methods that are called in parents --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../dotty/tools/dotc/core/Annotations.scala | 16 ++++ .../dotty/tools/dotc/core/Definitions.scala | 2 + .../tools/dotc/transform/init/Analyzer.scala | 2 +- .../tools/dotc/transform/init/Checker.scala | 60 +++++++------- .../tools/dotc/transform/init/Effects.scala | 7 -- .../tools/dotc/transform/init/Values.scala | 79 ++++++++++--------- .../tools/dotc/transform/init/package.scala | 12 ++- .../src/scala/annotation/internal/Call.scala | 16 ++++ .../neg-custom-args/safe-init/override1.scala | 8 +- .../neg-custom-args/safe-init/override2.scala | 18 +++-- .../neg-custom-args/safe-init/override3.scala | 6 +- .../neg-custom-args/safe-init/override4.scala | 4 + 13 files changed, 140 insertions(+), 92 deletions(-) create mode 100644 library/src/scala/annotation/internal/Call.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index ec5be939881a..fdfced5cc6eb 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -39,6 +39,7 @@ class Compiler { List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new PostTyper) :: // Additional checks and cleanups after type checking List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks + List(new init.Checker) :: // Check safe initialization of class fields Nil /** Phases dealing with TASTY tree pickling and unpickling */ @@ -49,7 +50,6 @@ class Compiler { /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = - List(new init.Checker) :: // Check safe initialization of class fields List(new FirstTransform, // Some transformations to put trees into a canonical form 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 diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 6a081898ed03..c616fa56a405 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -177,6 +177,22 @@ object Annotations { else None } + /** Extractor for init annotations */ + object Call { + /** A regular, non-deferred Child annotation */ + def apply(sym: Symbol)(implicit ctx: Context): Annotation = { + val tree = New(defn.CallAnnotType.appliedTo(sym.owner.thisType.select(sym.name, sym)), Nil) + ConcreteAnnotation(tree) + } + + def unapply(ann: Annotation)(implicit ctx: Context): Option[Symbol] = + if (ann.symbol == defn.CallAnnot) { + val AppliedType(tycon, (arg: NamedType) :: Nil) = ann.tree.tpe + Some(arg.symbol) + } + else None + } + def makeSourceFile(path: String)(implicit ctx: Context): Annotation = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 6af460b43dfc..e4329c7ba9d2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -812,6 +812,8 @@ class Definitions { def FilledAnnot(implicit ctx: Context): ClassSymbol = FilledAnnotType.symbol.asClass lazy val InitAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.init") def InitAnnot(implicit ctx: Context): ClassSymbol = InitAnnotType.symbol.asClass + lazy val CallAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Call") + def CallAnnot(implicit ctx: Context): ClassSymbol = CallAnnotType.symbol.asClass // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 37f149090885..78f0ca37eac1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -263,7 +263,7 @@ class Analyzer extends Indexer { analyzer => def checkSuper(tree: Tree, supert: Super, env: Env)(implicit ctx: Context): Res = { val SuperType(thistpe, supertpe) = supert.tpe val thisRef = checkRef(thistpe, env, tree.pos) - thisRef.value.select(tree.symbol, env.heap, tree.pos, isSuper = true) + thisRef.value.select(tree.symbol, env.heap, tree.pos, isStaticDispatch = true) } object NewEx { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 250aaaf524a5..5224f0677630 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -55,12 +55,12 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (cls.hasAnnotation(defn.UncheckedAnnot)) return tree def lateInitMsg(sym: Symbol) = - s"""|Initialization too late: $sym may be used during parent initialization. + s"""|Initialization too late: $sym is used during parent initialization. |Consider make it a class parameter.""" .stripMargin for (decl <- cls.info.decls.toList if decl.is(AnyFlags, butNot = Method | Deferred)) { - if (!decl.is(ParamAccessor | Override) && decl.isOverride) + if (!decl.is(ParamAccessor | Override) && decl.isCalledAbove(cls)) ctx.warning(lateInitMsg(decl), decl.pos) } @@ -77,7 +77,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => seenClasses.addEntry(cls) for (mbr <- cls.info.decls) if (mbr.isTerm && mbr.is(Deferred | Method) && - (mbr.hasAnnotation(defn.PartialAnnot) || mbr.hasAnnotation(defn.FilledAnnot)) && + (mbr.hasAnnotation(defn.PartialAnnot) || mbr.hasAnnotation(defn.InitAnnot)) && !membersToCheck.contains(mbr.name)) membersToCheck = membersToCheck.updated(mbr.name, mbr.info.asSeenFrom(self, mbr.owner)) parents(cls).foreach(addDecls) @@ -86,7 +86,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def invalidImplementMsg(sym: Symbol) = s"""|@scala.annotation.partial required for ${sym.show} in ${sym.owner.show} - |Because the abstract method it implements is marked as `@partial` or `@filled`.""" + |Because the abstract method it implements is marked as `@partial` or `@init`.""" .stripMargin for (name <- membersToCheck.keys) { @@ -97,9 +97,9 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } { val mbr = mbrd.symbol if (mbr.owner.ne(cls) && - !mbr.isOverride && + !mbr.isCalledAbove(cls) && !mbr.hasAnnotation(defn.PartialAnnot) && - !mbr.hasAnnotation(defn.FilledAnnot) ) + !mbr.hasAnnotation(defn.InitAnnot) ) ctx.warning(invalidImplementMsg(mbr), cls.pos) } } @@ -143,8 +143,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => // if (!sym.is(Deferred)) ctx.warning(s"field ${sym.name} is not initialized", sym.pos) // } - // filled check: try commit early - if (obj.open) filledCheck(obj, tmpl, root.heap) + // init check: try commit early + if (obj.open) initCheck(cls, obj, tmpl, root.heap) } def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { @@ -169,27 +169,30 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } def checkMethod(sym: Symbol): Unit = { - if (!sym.isPartial && !sym.isOverride) return + if (!sym.isPartial && !sym.isCalledAbove(cls)) { + println(s"$sym in ${sym.owner} not partial") + return + } val heap2 = heap.clone - var res = obj.select(sym, heap2, sym.pos) + var res = obj.select(sym, heap2, sym.pos, isStaticDispatch = true) if (!sym.info.isParameterless) res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) if (res.hasErrors) { - ctx.warning("Calling the partial method causes errors", sym.pos) + ctx.warning("Calling the method during initialization causes errors", sym.pos) res.effects.foreach(_.report) } else if (res.value != FullValue) { - ctx.warning("Partial method must return a full value", sym.pos) + ctx.warning("A method called during initialization must return a fully initialized value", sym.pos) } } def checkLazy(sym: Symbol): Unit = { - if (!sym.isPartial && !sym.isOverride) return + if (!sym.isPartial && !sym.isCalledAbove(cls)) return val heap2 = heap.clone - val res = obj.select(sym, heap2, sym.pos) + val res = obj.select(sym, heap2, sym.pos, isStaticDispatch = true) if (res.hasErrors) { ctx.warning("Forcing partial lazy value causes errors", sym.pos) res.effects.foreach(_.report) @@ -207,45 +210,46 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => checkLazy(vdef.symbol) case _ => } + + if (obj.open) obj.annotate(cls) } - def filledCheck(obj: ObjectValue, tmpl: tpd.Template, heap: Heap)(implicit ctx: Context) = { + def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template, heap: Heap)(implicit ctx: Context) = { def checkMethod(sym: Symbol): Unit = { - if (sym.isPartial || sym.isOverride || !sym.isFilled) return + if (!sym.isInit) return - var res = obj.select(sym, heap, sym.pos) + var res = obj.select(sym, heap, sym.pos, isStaticDispatch = true) if (!sym.info.isParameterless) res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) if (res.hasErrors) { - ctx.warning("Calling the filled method causes errors", sym.pos) + ctx.warning("Calling the init method causes errors", sym.pos) res.effects.foreach(_.report) } else if (res.value != FullValue) { - ctx.warning("Filled method must return a full value", sym.pos) + ctx.warning("An init method must return a full value", sym.pos) } } def checkLazy(sym: Symbol): Unit = { - if (sym.isPartial || sym.isOverride || !sym.isFilled) return + if (!sym.isInit) return - val res = obj.select(sym, heap, sym.pos) + val res = obj.select(sym, heap, sym.pos, isStaticDispatch = true) if (res.hasErrors) { - ctx.warning("Forcing filled lazy value causes errors", sym.pos) + ctx.warning("Forcing init lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { val value = res.value.widen(heap, sym.pos) - if (value != FullValue) ctx.warning("Filled lazy value must return a full value", sym.pos) + if (value != FullValue) ctx.warning("Init lazy value must return a full value", sym.pos) } } def checkValDef(sym: Symbol): Unit = { - if (sym.is(Flags.PrivateOrLocal) || sym.hasAnnotation(defn.UncheckedAnnot)) return + if (sym.is(Flags.PrivateOrLocal)) return - val isOverride = sym.allOverriddenSymbols.exists(sym => sym.isInit) val expected: OpaqueValue = - if (isOverride) FullValue - else sym.info.value.join(sym.value) + if (sym.isCalledAbove(cls)) FullValue + else sym.value val actual = obj.select(sym, heap, sym.pos).value.widen(heap, sym.pos) if (actual < expected) ctx.warning(s"Found = $actual, expected = $expected" , sym.pos) @@ -260,6 +264,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => checkValDef(vdef.symbol) case _ => } + + if (obj.open) obj.annotate(cls) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 3a96b27526c1..017c0f3fe8a8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -61,8 +61,6 @@ sealed trait Effect { def report(implicit ctx: Context): Unit = this match { case Uninit(sym, pos) => ctx.warning(s"Reference to uninitialized value `${sym.name}`", pos) - case OverrideRisk(sym, pos) => - ctx.warning(s"Reference to $sym which could be overriden. Consider make the method final or annotate it with `@partial` or `@filled` for safe overriding", pos) case Call(sym, effects, pos) => ctx.warning(s"The call to `${sym.name}` causes initialization problem", pos) effects.foreach(_.report) @@ -72,20 +70,15 @@ sealed trait Effect { case Instantiate(cls, effs, pos) => ctx.warning(s"Create instance results in initialization errors", pos) effs.foreach(_.report) - case UseAbstractDef(sym, pos) => - ctx.warning(s"`@scala.annotation.init` is recommended for abstract $sym for safe initialization", sym.pos) - ctx.warning(s"Reference to abstract $sym which should be annotated with `@scala.annotation.init`", pos) case Generic(msg, pos) => ctx.warning(msg, pos) } } case class Uninit(sym: Symbol, pos: Position) extends Effect // usage of uninitialized values -case class OverrideRisk(sym: Symbol, pos: Position) extends Effect // calling methods that are not override-free case class Call(sym: Symbol, effects: Seq[Effect], pos: Position) extends Effect // calling method results in error case class Force(sym: Symbol, effects: Seq[Effect], pos: Position) extends Effect // force lazy val results in error case class Instantiate(cls: Symbol, effs: Seq[Effect], pos: Position) extends Effect // create new instance of in-scope inner class results in error -case class UseAbstractDef(sym: Symbol, pos: Position) extends Effect // use abstract def during initialization, see override5.scala case class Generic(msg: String, pos: Position) extends Effect // generic problem object Effect { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index d12aaab55013..c9a4bbce4380 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -13,6 +13,7 @@ import Symbols._ import Types._ import Decorators._ import util.Positions._ +import Annotations._ import config.Printers.init.{ println => debug } import collection.mutable @@ -46,7 +47,7 @@ object Value { /** Abstract values in analysis */ sealed trait Value { /** Select a member on a value */ - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean = false)(implicit ctx: Context): Res + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean = false)(implicit ctx: Context): Res /** Assign on a value */ def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res @@ -102,7 +103,7 @@ sealed trait Value { case sv: SliceValue => heap(sv.id).asSlice.widen case ov: ObjectValue => - if (ov.open) FilledValue + if (ov.open) PartialValue else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => if (acc != FullValue) return FilledValue recur(v, heap).join(acc) @@ -124,7 +125,7 @@ sealed trait Value { /** The value is absent */ object NoValue extends Value { def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = ??? def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? @@ -142,9 +143,9 @@ case class UnionValue(val values: Set[SingleValue]) extends Value { } } - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { values.foldLeft(Res()) { (acc, value) => - value.select(sym, heap, pos, isSuper).join(acc) + value.select(sym, heap, pos, isStaticDispatch).join(acc) } } @@ -187,7 +188,7 @@ abstract sealed class OpaqueValue extends SingleValue { } object FullValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) else Res() @@ -214,7 +215,7 @@ object FullValue extends OpaqueValue { } object PartialValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) @@ -234,7 +235,7 @@ object PartialValue extends OpaqueValue { } else { // field select if (!sym.isPrimaryConstructorFields || sym.owner.is(Flags.Trait)) - res += Generic(s"Cannot access field $sym on a partial object", pos) + res += Generic(s"The $sym may not be initialized", pos) } res @@ -266,22 +267,22 @@ object PartialValue extends OpaqueValue { } object FilledValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { val res = Res() if (sym.is(Flags.Method)) { - if (!sym.isPartial && !sym.isFilled && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@partial` or `@filled` in order to be called", pos) + if (!sym.isPartial && !sym.isInit && !sym.isCalledIn(sym.owner.asClass) && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@init` in order to be called", pos) res.value = Value.defaultFunctionValue(sym) } - else if (sym.is(Flags.Lazy)) { + else if (sym.is(Flags.Lazy) && !sym.isCalledIn(sym.owner.asClass)) { if (!sym.isPartial && !sym.isFilled) - res += Generic(s"The lazy field $sym should be marked as `@partial` or `@filled` in order to be accessed", pos) + res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", pos) res.value = sym.info.value } else { - res.value = sym.value.join(sym.info.value) + res.value = sym.value } res @@ -299,7 +300,7 @@ object FilledValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isPartial && !cls.isFilled) { - res += Generic(s"The nested $cls should be marked as `@partial` or `@filled` in order to be instantiated", pos) + res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", pos) res.value = FullValue return res } @@ -318,7 +319,7 @@ object FilledValue extends OpaqueValue { abstract class FunctionValue extends SingleValue { self => def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = sym.name match { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = sym.name match { case nme.apply | nme.lift => Res(value = this) case nme.compose => val selectedFun = new FunctionValue() { @@ -429,7 +430,7 @@ abstract class FunctionValue extends SingleValue { self => /** A lazy value */ abstract class LazyValue extends SingleValue { // not supported - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = ??? def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? @@ -443,7 +444,7 @@ class SliceValue(val id: Int) extends SingleValue { /** not supported, impossible to apply an object value */ def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { val slice = heap(id).asSlice val value = slice(sym) @@ -467,12 +468,7 @@ class SliceValue(val id: Int) extends SingleValue { else Res(effects = Vector(Uninit(sym, pos))) } else { - val res = Res(value = value) - - if (sym.is(Flags.Deferred) && !sym.hasAnnotation(defn.InitAnnot)) - res += UseAbstractDef(sym, pos) - - res + Res(value = value) } } } @@ -505,6 +501,9 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { private var _slices: Map[ClassSymbol, Value] = Map() def slices: Map[ClassSymbol, Value] = _slices + private var _dynamicCalls: Set[Symbol] = Set.empty + def dynamicCalls: Set[Symbol] = _dynamicCalls + def add(cls: ClassSymbol, value: Value) = { if (slices.contains(cls)) { _slices = _slices.updated(cls, _slices(cls).join(value)) @@ -514,7 +513,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { // handle dynamic dispatch private def resolve(sym: Symbol)(implicit ctx: Context): Symbol = { - if (sym.isClass || sym.isConstructor || sym.isEffectivelyFinal || sym.is(Flags.Private)) sym + if (sym.isClass || sym.isConstructor || sym.isEffectivelyFinal) sym else { // the method may crash, see tests/pos/t7517.scala try sym.matchingMember(tp) catch { case _: Throwable => NoSymbol } @@ -524,8 +523,8 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { /** not supported, impossible to apply an object value */ def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isSuper: Boolean)(implicit ctx: Context): Res = { - val target = if (isSuper) sym else resolve(sym) + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + val target = if (isStaticDispatch) sym else resolve(sym) // select on self type if (!target.exists) { @@ -537,23 +536,18 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (this.widen(heap, pos) == FullValue) return FullValue.select(sym, heap, pos) + // remember dynamic calls + if (!isStaticDispatch && !target.isEffectivelyFinal) { + _dynamicCalls = _dynamicCalls + target + } + val cls = target.owner.asClass if (slices.contains(cls)) { - val res = slices(cls).select(target, heap, pos) - // ignore field access, but field access in Scala - // are method calls, thus is unsafe as well - if (!isSuper && open && target.is(Flags.Method, butNot = Flags.Lazy) && - !target.isPartial && - !target.isFilled && - !target.isOverride && - !target.isEffectivelyFinal && - !target.name.is(DefaultGetterName)) - res += OverrideRisk(target, pos) - res + slices(cls).select(target, heap, pos) } else { // select on unknown super - assert (target.isDefinedOn(tp)) + assert(target.isDefinedOn(tp)) FilledValue.select(target, heap, pos) } } @@ -591,4 +585,11 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") "Object {\n" + setting.indent(body) + "\n}" } + + def annotate(cls: ClassSymbol)(implicit ctx: Context) = { + dynamicCalls.foreach { sym => + debug(s"$sym used during initialization of $cls") + cls.addAnnotation(Annotation.Call(sym)) + } + } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 24540afa917b..ad76ed1eccce 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -16,6 +16,7 @@ import Denotations._ import SymDenotations._ import Types._ import Decorators._ +import Annotations._ import util.Positions._ import Constants.Constant import collection.mutable @@ -35,9 +36,14 @@ package object init { def isPartial(implicit ctx: Context) = sym.hasAnnotation(defn.PartialAnnot) def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) - def isOverride(implicit ctx: Context) = - (sym.is(Method) && sym.allOverriddenSymbols.exists(sym => sym.isPartial || sym.isFilled)) || - (!sym.is(Method) && sym.allOverriddenSymbols.exists(sym => sym.isPartial || sym.isFilled || sym.isInit)) + def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = + cls.self.annotations.exists({ + case Annotation.Call(mthSym) => mthSym == sym + case _ => false + }) || sym.allOverriddenSymbols.exists(_.isCalledIn(cls)) + + def isCalledAbove(from: ClassSymbol)(implicit ctx: Context) = + from.baseClasses.tail.exists(cls => sym.isCalledIn(cls)) def isPrimaryConstructorFields(implicit ctx: Context) = sym.is(ParamAccessor) diff --git a/library/src/scala/annotation/internal/Call.scala b/library/src/scala/annotation/internal/Call.scala new file mode 100644 index 000000000000..b2618bbb8d3d --- /dev/null +++ b/library/src/scala/annotation/internal/Call.scala @@ -0,0 +1,16 @@ +package scala.annotation.internal + +import scala.annotation.Annotation + +/** An annotation to indicate a dynamic call is made during initialization of the object. + * E.g. if we have + * + * abstract class A { + * def f: Int + * val x = f + * } + * + * Then the class symbol `A` would carry the annotations `@Call[f]` where `f` + * is a TermRef referring to the term symbol of `f`. + */ +class Call[T] extends Annotation diff --git a/tests/neg-custom-args/safe-init/override1.scala b/tests/neg-custom-args/safe-init/override1.scala index 1c57aa848011..082267a54324 100644 --- a/tests/neg-custom-args/safe-init/override1.scala +++ b/tests/neg-custom-args/safe-init/override1.scala @@ -1,8 +1,8 @@ trait Foo { - val x = "world" + val x = 20 foo(5) // ok - def foo(n: Int): String + def foo(n: Int): Int } @@ -15,6 +15,6 @@ class Qux(x: Int) extends Bar { } class Yun extends Bar { - val x: Int = 10 - def foo(n: Int) = x + n // error + override val x: Int = 10 + def foo(n: Int) = x + n // error // error } diff --git a/tests/neg-custom-args/safe-init/override2.scala b/tests/neg-custom-args/safe-init/override2.scala index f5b71e8cf057..c7794aa84ef1 100644 --- a/tests/neg-custom-args/safe-init/override2.scala +++ b/tests/neg-custom-args/safe-init/override2.scala @@ -2,20 +2,26 @@ trait Foo { val x = "world" foo(5) - def bar(x: Int): Int + def bar(x: Int): Int = 20 - private def foo(n: Int): String = x + n + def foo(n: Int): String = x + n } -abstract class Bar extends Foo { +class Bar extends Foo { val y = "hello" foo(5) bar(10) // error - def foo(n: Int): String = { // need to be private or final + override def foo(n: Int): String = { // error println("in foo") - println(y.size) - y + x + y + x // error // error } } + +class Qux extends Foo { + val y = "hello" + + foo(5) + bar(10) // error +} diff --git a/tests/neg-custom-args/safe-init/override3.scala b/tests/neg-custom-args/safe-init/override3.scala index 759c6937045c..c169cb9f4582 100644 --- a/tests/neg-custom-args/safe-init/override3.scala +++ b/tests/neg-custom-args/safe-init/override3.scala @@ -27,8 +27,6 @@ class Bar2 extends Foo { final def foo(n: Int) = "world" } -class Bar3 extends Foo { - val m = "hello" - - final def foo(n: Int) = m + "world" // error // error +class Bar3(m: String) extends Foo { + final def foo(n: Int) = m + "world" } diff --git a/tests/neg-custom-args/safe-init/override4.scala b/tests/neg-custom-args/safe-init/override4.scala index f52dde03ac55..b82d7c9f6b63 100644 --- a/tests/neg-custom-args/safe-init/override4.scala +++ b/tests/neg-custom-args/safe-init/override4.scala @@ -35,4 +35,8 @@ abstract class Foo2 { @partial def enter(k: Int, v: String) = map(k) = v // error // error +} + +class Qux extends Foo2 { + val map: mutable.Map[Int, String] = mutable.Map.empty } \ No newline at end of file From 8961a31b245e412a5aaf0cf25deac695c4dcfbdd Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 22 Oct 2018 15:51:40 +0200 Subject: [PATCH 07/83] WIP - handle abstract fields --- compiler/src/dotty/tools/dotc/transform/init/Checker.scala | 2 +- compiler/src/dotty/tools/dotc/transform/init/Indexer.scala | 2 +- tests/neg-custom-args/safe-init/override5.scala | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 5224f0677630..854dea7bd9d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -251,7 +251,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.isCalledAbove(cls)) FullValue else sym.value - val actual = obj.select(sym, heap, sym.pos).value.widen(heap, sym.pos) + val actual = obj.select(sym, heap, sym.pos, isStaticDispatch = true).value.widen(heap, sym.pos) if (actual < expected) ctx.warning(s"Found = $actual, expected = $expected" , sym.pos) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index e2154f774137..d180ca00808b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -87,7 +87,7 @@ trait Indexer { self: Analyzer => case vdef: ValDef if vdef.symbol.is(Lazy) => slice.add(vdef.symbol, lazyValue(vdef, slice.innerEnv)) case vdef: ValDef => - val value = if (vdef.symbol.isInit) FullValue else NoValue + val value = if (vdef.symbol.isInit || vdef.symbol.is(Deferred)) FullValue else NoValue slice.add(vdef.symbol, value) case tdef: TypeDef if tdef.isClassDef => // class has to be handled differently because of inheritance diff --git a/tests/neg-custom-args/safe-init/override5.scala b/tests/neg-custom-args/safe-init/override5.scala index cc3410784c88..8490b19cac68 100644 --- a/tests/neg-custom-args/safe-init/override5.scala +++ b/tests/neg-custom-args/safe-init/override5.scala @@ -5,7 +5,7 @@ trait Foo { } class Bar extends Foo { - val name = "Jack" // error: partial cannot be implemented by val + val name = "Jack" // error: init too late } @@ -16,7 +16,7 @@ trait Zen { } class Tao extends Zen { - val name = "Jack" // error: init cannot be implemented by val + val name = "Jack" // error: init too late } From a9d9b86fa40fecf08f94d996aa90bf2a7738505d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 22 Oct 2018 17:44:24 +0200 Subject: [PATCH 08/83] overridding tests pass --- .../tools/dotc/transform/init/Checker.scala | 120 +++++++----------- .../tools/dotc/transform/init/Values.scala | 72 ++++++++++- .../tools/dotc/transform/init/package.scala | 17 ++- .../neg-custom-args/safe-init/override3.scala | 3 - .../neg-custom-args/safe-init/override7.scala | 2 +- .../neg-custom-args/safe-init/override8.scala | 8 +- 6 files changed, 133 insertions(+), 89 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 854dea7bd9d3..6ba98630b6d4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -64,45 +64,28 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => ctx.warning(lateInitMsg(decl), decl.pos) } - var membersToCheck: util.SimpleIdentityMap[Name, Type] = util.SimpleIdentityMap.Empty[Name] - val seenClasses = new util.HashSet[Symbol](256) - - def parents(cls: Symbol) = - cls.info.parents.map(_.classSymbol) - .filter(_.is(AbstractOrTrait)) - .dropWhile(_.is(JavaDefined | Scala2x)) - - def addDecls(cls: Symbol): Unit = - if (!seenClasses.contains(cls)) { - seenClasses.addEntry(cls) - for (mbr <- cls.info.decls) - if (mbr.isTerm && mbr.is(Deferred | Method) && - (mbr.hasAnnotation(defn.PartialAnnot) || mbr.hasAnnotation(defn.InitAnnot)) && - !membersToCheck.contains(mbr.name)) - membersToCheck = membersToCheck.updated(mbr.name, mbr.info.asSeenFrom(self, mbr.owner)) - parents(cls).foreach(addDecls) - } - parents(cls).foreach(addDecls) // no need to check methods defined in current class - - def invalidImplementMsg(sym: Symbol) = - s"""|@scala.annotation.partial required for ${sym.show} in ${sym.owner.show} - |Because the abstract method it implements is marked as `@partial` or `@init`.""" + def invalidImplementMsg(sym: Symbol) = { + val annot = if (sym.owner.is(Trait)) "partial" else "init" + s"""|@scala.annotation.$annot required for ${sym.show} in ${sym.owner.show} + |Because the method is called during initialization.""" .stripMargin + } + + def parents(cls: ClassSymbol) = + cls.baseClasses.tail.filter(_.is(AbstractOrTrait)).dropWhile(_.is(JavaDefined | Scala2x)) - for (name <- membersToCheck.keys) { - val tp = membersToCheck(name) + def check(curCls: ClassSymbol): Unit = { for { - mbrd <- self.member(name).alternatives - if mbrd.info.overrides(tp, matchLoosely = true) - } { - val mbr = mbrd.symbol - if (mbr.owner.ne(cls) && - !mbr.isCalledAbove(cls) && - !mbr.hasAnnotation(defn.PartialAnnot) && - !mbr.hasAnnotation(defn.InitAnnot) ) - ctx.warning(invalidImplementMsg(mbr), cls.pos) - } + mbr <- calledSymsIn(curCls) + mbrd <- self.member(mbr.name).alternatives + tp = mbr.info.asSeenFrom(self, mbr.owner) + if mbrd.info.overrides(tp, matchLoosely = true) && + !mbrd.symbol.isInit && !mbrd.symbol.isPartial & + !mbrd.symbol.isCalledAbove(cls.asClass) && + !mbrd.symbol.is(Deferred) + } ctx.warning(invalidImplementMsg(mbrd.symbol), cls.pos) } + parents(cls).foreach(check) // no need to check methods defined in current class checkInit(cls, tree) @@ -139,12 +122,11 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val slice = root.heap(sliceValue.id).asSlice res.effects.foreach(_.report) - // slice.notAssigned.foreach { sym => - // if (!sym.is(Deferred)) ctx.warning(s"field ${sym.name} is not initialized", sym.pos) - // } // init check: try commit early if (obj.open) initCheck(cls, obj, tmpl, root.heap) + + if (obj.open) obj.annotate(cls) } def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { @@ -152,32 +134,18 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => // enhancement possible to check if there are actual children // and whether children are possible in other modules. - val root = Heap.createRootEnv - val heap = root.heap - val slice = root.newSlice(cls) - analyzer.indexMembers(tmpl.body, slice) - slice.innerEnv.add(cls, obj) - indexOuter(cls, root) + def checkMethod(ddef: tpd.DefDef): Unit = { + val sym = ddef.symbol + if (!sym.isPartial && !sym.isCalledAbove(cls)) return - obj.add(cls, new SliceValue(slice.id)) - cls.baseClasses.tail.foreach(base => obj.add(base, PartialValue)) + val root = Heap.createRootEnv + val heap = root.heap + indexOuter(cls, root) + if (sym.isPartial) root.add(cls, BlankValue) + else root.add(cls, PartialValue) - if (!cls.is(Trait)) - tmpl.constr.vparamss.flatten.zipWithIndex.foreach { case (param: ValDef, index) => - val sym = cls.info.member(param.name).suchThat(x => !x.is(Method)).symbol - if (sym.exists) slice.add(sym, sym.info.value) - } - - def checkMethod(sym: Symbol): Unit = { - if (!sym.isPartial && !sym.isCalledAbove(cls)) { - println(s"$sym in ${sym.owner} not partial") - return - } - - val heap2 = heap.clone - var res = obj.select(sym, heap2, sym.pos, isStaticDispatch = true) - if (!sym.info.isParameterless) - res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + val value = analyzer.methodValue(ddef, root) + val res = value.apply(i => FullValue, i => NoPosition, sym.pos, heap) if (res.hasErrors) { ctx.warning("Calling the method during initialization causes errors", sym.pos) @@ -188,36 +156,40 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } } - def checkLazy(sym: Symbol): Unit = { + def checkLazy(vdef: tpd.ValDef): Unit = { + val sym = vdef.symbol if (!sym.isPartial && !sym.isCalledAbove(cls)) return - val heap2 = heap.clone - val res = obj.select(sym, heap2, sym.pos, isStaticDispatch = true) + val root = Heap.createRootEnv + val heap = root.heap + indexOuter(cls, root) + if (sym.isPartial) root.add(cls, BlankValue) + else root.add(cls, PartialValue) + + val value = analyzer.lazyValue(vdef, root) + val res = value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + if (res.hasErrors) { ctx.warning("Forcing partial lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { - val value = res.value.widen(heap2, sym.pos) + val value = res.value.widen(heap, sym.pos) if (value != FullValue) ctx.warning("Partial lazy value must return a full value", sym.pos) } } tmpl.body.foreach { case ddef: DefDef if !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => - checkMethod(ddef.symbol) + checkMethod(ddef) case vdef: ValDef if vdef.symbol.is(Lazy) => - checkLazy(vdef.symbol) + checkLazy(vdef) case _ => } - - if (obj.open) obj.annotate(cls) } def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template, heap: Heap)(implicit ctx: Context) = { def checkMethod(sym: Symbol): Unit = { - if (!sym.isInit) return - var res = obj.select(sym, heap, sym.pos, isStaticDispatch = true) if (!sym.info.isParameterless) res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) @@ -256,7 +228,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } tmpl.body.foreach { - case ddef: DefDef if ddef.symbol.isFilled && !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => + case ddef: DefDef if ddef.symbol.isInit && !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => checkMethod(ddef.symbol) case vdef: ValDef if vdef.symbol.is(Lazy) => checkLazy(vdef.symbol) @@ -264,8 +236,6 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => checkValDef(vdef.symbol) case _ => } - - if (obj.open) obj.annotate(cls) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index c9a4bbce4380..ea124bee5539 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -69,6 +69,8 @@ sealed trait Value { case (v, FullValue) => v case (NoValue, _) => NoValue case (_, NoValue) => NoValue + case (BlankValue, _) => BlankValue + case (_, BlankValue) => BlankValue case (PartialValue, _) => PartialValue case (_, PartialValue) => PartialValue case (v1: OpaqueValue, v2: OpaqueValue) => v1.join(v2) @@ -214,6 +216,74 @@ object FullValue extends OpaqueValue { override def toString = "full value" } +/** A blank value, where class/trait params are not yet initialized + * + * abstract class A { + * def f: Int + * val a = f + * } + * + * trait B(x: 20) { + * def f: Int = x // error: `x` is not initialized yet + * } + * + * class C extends A with B(20) + */ +object BlankValue extends OpaqueValue { + def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + // set state to Full, don't report same error message again + val res = Res(value = FullValue) + + if (sym.is(Flags.Method)) { + if (!sym.isPartial && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@partial` in order to be called", pos) + + res.value = Value.defaultFunctionValue(sym) + } + else if (sym.is(Flags.Lazy)) { + if (!sym.isPartial) + res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", pos) + } + else if (sym.isClass) { + if (!sym.isPartial) + res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", pos) + } + else { // field select + res += Generic(s"The $sym may not be initialized", pos) + } + + res + } + + /** assign to partial is always fine? */ + def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = Res() + + def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + val paramInfos = constr.info.paramInfoss.flatten + val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + if (res.hasErrors) return res + + val cls = constr.owner.asClass + if (!cls.isPartial) { + res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", pos) + res.value = FullValue + return res + } + + obj.add(cls, FilledValue) + + Res() + } + + def show(setting: ShowSetting)(implicit ctx: Context): String = "Partial" + + override def toString = "blank value" +} + +/** A raw value, where class/trait params are initialized, but body fields are not + * + * TODO: rename to `raw` + */ object PartialValue extends OpaqueValue { def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { // set state to Full, don't report same error message again @@ -234,7 +304,7 @@ object PartialValue extends OpaqueValue { res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", pos) } else { // field select - if (!sym.isPrimaryConstructorFields || sym.owner.is(Flags.Trait)) + if (!sym.isClassParam) res += Generic(s"The $sym may not be initialized", pos) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index ad76ed1eccce..54c4f5bdfe75 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -24,6 +24,7 @@ import collection.mutable package object init { implicit class TypeOps(val tp: Type) extends AnyVal { def isPartial(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.PartialAnnot) + def isFilled(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.FilledAnnot) def value(implicit ctx: Context) = @@ -32,20 +33,26 @@ package object init { else FullValue } + def calledSymsIn(cls: ClassSymbol)(implicit ctx: Context): List[Symbol] = + cls.self.annotations.collect { + case Annotation.Call(sym) => sym + } + implicit class SymOps(val sym: Symbol) extends AnyVal { def isPartial(implicit ctx: Context) = sym.hasAnnotation(defn.PartialAnnot) + def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) + def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) + + def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = - cls.self.annotations.exists({ - case Annotation.Call(mthSym) => mthSym == sym - case _ => false - }) || sym.allOverriddenSymbols.exists(_.isCalledIn(cls)) + calledSymsIn(cls).exists(_ == sym) || sym.allOverriddenSymbols.exists(_.isCalledIn(cls)) def isCalledAbove(from: ClassSymbol)(implicit ctx: Context) = from.baseClasses.tail.exists(cls => sym.isCalledIn(cls)) - def isPrimaryConstructorFields(implicit ctx: Context) = sym.is(ParamAccessor) + def isClassParam(implicit ctx: Context) = sym.is(ParamAccessor) def isDefinedOn(tp: Type)(implicit ctx: Context): Boolean = tp.classSymbol.isSubClass(sym.owner) diff --git a/tests/neg-custom-args/safe-init/override3.scala b/tests/neg-custom-args/safe-init/override3.scala index c169cb9f4582..978a2c7e9906 100644 --- a/tests/neg-custom-args/safe-init/override3.scala +++ b/tests/neg-custom-args/safe-init/override3.scala @@ -1,11 +1,8 @@ -import scala.annotation.partial - trait Foo { println("init x") val x = "world" val y = foo(5) - @partial def foo(n: Int): String } diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala index 62b97f9c4af3..4ee0eb2dbad0 100644 --- a/tests/neg-custom-args/safe-init/override7.scala +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -11,7 +11,7 @@ class Bar(val name: String) extends Foo { def getName = name // ok: name is a Param field - def getTitle = title // error: title cannot use title // error + def getTitle = title // error: cannot use title // error } object Test { diff --git a/tests/neg-custom-args/safe-init/override8.scala b/tests/neg-custom-args/safe-init/override8.scala index d1435cbe618c..c97a049bec3e 100644 --- a/tests/neg-custom-args/safe-init/override8.scala +++ b/tests/neg-custom-args/safe-init/override8.scala @@ -30,18 +30,18 @@ class Tao { def msg = "can be overriden" + @scala.annotation.init def foo(n: Int) = m + msg } -class Zen extends Tao with Foo // error: Tao.foo needs to be `@init` +class Zen extends Tao with Foo class Lux { val m = "hello" def msg = "can be overriden" - @partial - def foo(n: Int) = m + msg // error // error // error + def foo(n: Int) = m + msg } -class Logos extends Lux with Foo \ No newline at end of file +class Logos extends Lux with Foo // error \ No newline at end of file From fcad3cda39a63e13e96c3f86ed468ffb30a24352 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 23 Oct 2018 17:58:18 +0200 Subject: [PATCH 09/83] WIP --- .../tools/dotc/transform/init/Analyzer.scala | 165 +++++----- .../tools/dotc/transform/init/Checker.scala | 73 ++--- .../tools/dotc/transform/init/Effects.scala | 2 +- .../tools/dotc/transform/init/Heap.scala | 30 +- .../tools/dotc/transform/init/Indexer.scala | 53 ++-- .../tools/dotc/transform/init/Setting.scala | 30 ++ .../dotc/transform/init/ShowSetting.scala | 4 +- .../tools/dotc/transform/init/Values.scala | 286 +++++++++--------- .../tools/dotc/transform/init/package.scala | 9 + 9 files changed, 358 insertions(+), 294 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Setting.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 78f0ca37eac1..9e60929aa4d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -29,14 +29,14 @@ class Analyzer extends Indexer { analyzer => var depth: Int = 0 - def trace(msg: => String, env: Env)(body: => Res)(implicit ctx: Context) = { + def trace(msg: => String)(body: => Res)(implicit setting: Setting) = { indentedDebug(s"==> ${pad(msg)}?") - indentedDebug("heap = " + env.heap.show) - indentedDebug(env.show(ShowSetting(env.heap))) + indentedDebug("heap = " + setting.heap.show) + indentedDebug(setting.env.show(setting.showSetting)) depth += 1 val res = body depth -= 1 - indentedDebug(s"<== ${pad(msg)} = ${pad(res.show(ShowSetting(env.heap)))}") + indentedDebug(s"<== ${pad(msg)} = ${pad(res.show(setting.showSetting))}") res } @@ -44,107 +44,107 @@ class Analyzer extends Indexer { analyzer => def indentedDebug(msg: => String) = debug(ShowSetting.pad(msg, depth, padFirst = true)) - def checkApply(tree: tpd.Tree, fun: Tree, argss: List[List[Tree]], env: Env)(implicit ctx: Context): Res = { + def checkApply(tree: tpd.Tree, fun: Tree, argss: List[List[Tree]])(implicit setting: Setting): Res = { val funSym = fun.symbol - val funRes = apply(fun, env) + val funRes = apply(fun) val args = argss.flatten val values = args.map { arg => - val res = apply(arg, env) + val res = apply(arg) funRes ++= res.effects res.value } indentedDebug(s">>> calling $funSym") - funRes.value(values, args.map(_.pos), tree.pos, env.heap) ++ funRes.effects + funRes.value(values, args.map(_.pos)) ++ funRes.effects } - def checkSelect(tree: Select, env: Env)(implicit ctx: Context): Res = { - val prefixRes = apply(tree.qualifier, env) - val res = prefixRes.value.select(tree.symbol, env.heap, tree.pos) + def checkSelect(tree: Select)(implicit setting: Setting): Res = { + val prefixRes = apply(tree.qualifier) + val res = prefixRes.value.select(tree.symbol) res.effects = prefixRes.effects ++ res.effects res } - private def enclosedIn(curSym: Symbol, inSym: Symbol)(implicit ctx: Context): Boolean = + private def enclosedIn(curSym: Symbol, inSym: Symbol)(implicit setting: Setting): Boolean = curSym.exists && ((curSym `eq` inSym) || (enclosedIn(curSym.owner, inSym))) - def checkRef(tp: Type, env: Env, pos: Position)(implicit ctx: Context): Res = trace("checking " + tp.show, env)(tp match { - case tp : TermRef if tp.symbol.is(Module) && enclosedIn(ctx.owner, tp.symbol.moduleClass) => + def checkRef(tp: Type)(implicit setting: Setting): Res = trace("checking " + tp.show)(tp match { + case tp : TermRef if tp.symbol.is(Module) && enclosedIn(setting.ctx.owner, tp.symbol.moduleClass) => // self reference by name: object O { ... O.xxx } - checkRef(ThisType.raw(tp.symbol.moduleClass.typeRef), env, pos) + checkRef(ThisType.raw(tp.symbol.moduleClass.typeRef)) case tp @ TermRef(NoPrefix, _) => - env.select(tp.symbol, pos) + setting.env.select(tp.symbol) case tp @ TermRef(prefix, _) => - val res = checkRef(prefix, env, pos) - res.value.select(tp.symbol, env.heap, pos) + val res = checkRef(prefix) + res.value.select(tp.symbol) case tp @ ThisType(tref) => val cls = tref.symbol if (cls.is(Package)) Res() // Dotty represents package path by ThisType - else if (env.contains(cls)) Res(value = env(cls)) + else if (setting.env.contains(cls)) Res(value = setting.env(cls)) else { // ThisType used outside of class scope, can happen for objects // see tests/pos/t2712-7.scala - assert(cls.is(Flags.Module) && !enclosedIn(ctx.owner, cls)) + assert(cls.is(Flags.Module) && !enclosedIn(setting.ctx.owner, cls)) Res() } }) - def checkClosure(sym: Symbol, tree: Tree, env: Env)(implicit ctx: Context): Res = { - if (env.contains(sym)) Res(value = env(sym)) else Res() + def checkClosure(sym: Symbol, tree: Tree)(implicit setting: Setting): Res = { + if (setting.env.contains(sym)) Res(value = setting.env(sym)) else Res() } - def checkIf(tree: If, env: Env)(implicit ctx: Context): Res = { + def checkIf(tree: If)(implicit setting: Setting): Res = { val If(cond, thenp, elsep) = tree - val condRes: Res = apply(cond, env) + val condRes: Res = apply(cond) def makeFun(body: Tree) = new FunctionValue { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { - val envCurrent = heap(env.id).asEnv - analyzer.apply(body, envCurrent) + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { + analyzer.apply(body) } } val thenFun = makeFun(thenp) val elseFun = makeFun(elsep) - val res = thenFun.join(elseFun).apply(Nil, Nil, NoPosition, env.heap) + val res = thenFun.join(elseFun).apply(Nil, Nil) res ++= condRes.effects res } - def checkValDef(vdef: ValDef, env: Env)(implicit ctx: Context): Res = { + def checkValDef(vdef: ValDef)(implicit setting: Setting): Res = { val rhsRes = if (tpd.isWildcardArg(vdef.rhs)) Res(value = NoValue) - else apply(vdef.rhs, env) + else apply(vdef.rhs) val sym = vdef.symbol sym.termRef match { case tp @ TermRef(NoPrefix, _) => - env.assign(tp.symbol, rhsRes.value, vdef.rhs.pos) + setting.env.assign(tp.symbol, rhsRes.value)(setting.withPos(vdef.rhs.pos)) case tp @ TermRef(prefix, _) => - val prefixRes = checkRef(prefix, env, vdef.rhs.pos) + val prefixRes = checkRef(prefix)(setting.withPos(vdef.rhs.pos)) assert(!prefixRes.hasErrors) - prefixRes.value.assign(sym, rhsRes.value, env.heap, vdef.pos) + prefixRes.value.assign(sym, rhsRes.value)(setting.withPos(vdef.rhs.pos)) } Res(effects = rhsRes.effects) } - def checkStats(stats: List[Tree], env: Env)(implicit ctx: Context): Res = + def checkStats(stats: List[Tree])(implicit setting: Setting): Res = stats.foldLeft(Res()) { (acc, stat) => - indentedDebug(s"acc = ${pad(acc.show(ShowSetting(env.heap)))}") - val res1 = apply(stat, env) + indentedDebug(s"acc = ${pad(acc.show(ShowSetting(setting.env.heap, setting.ctx)))}") + val res1 = apply(stat) acc.copy(effects = acc.effects ++ res1.effects) } - def checkBlock(tree: Block, env: Env)(implicit ctx: Context): Res = { - val newEnv = env.fresh() - indexStats(tree.stats, newEnv) + def checkBlock(tree: Block)(implicit setting: Setting): Res = { + val newEnv = setting.env.fresh() + val setting2 = setting.withEnv(newEnv) + indexStats(tree.stats)(setting2) - val res1 = checkStats(tree.stats, newEnv) - val res2 = apply(tree.expr, newEnv) + val res1 = checkStats(tree.stats)(setting2) + val res2 = apply(tree.expr)(setting2) res2.copy(effects = res1.effects ++ res2.effects) } @@ -158,28 +158,28 @@ class Analyzer extends Indexer { analyzer => res } - def checkAssign(lhs: Tree, rhs: Tree, env: Env)(implicit ctx: Context): Res = { - val rhsRes = apply(rhs, env) + def checkAssign(lhs: Tree, rhs: Tree)(implicit setting: Setting): Res = { + val rhsRes = apply(rhs) if (rhsRes.hasErrors) return rhsRes lhs match { case ident @ Ident(_) => ident.tpe match { case tp @ TermRef(NoPrefix, _) => - env.assign(tp.symbol, rhsRes.value, rhs.pos) + setting.env.assign(tp.symbol, rhsRes.value)(setting.withPos(rhs.pos)) case tp @ TermRef(prefix, _) => - val prefixRes = checkRef(prefix, env, rhs.pos) + val prefixRes = checkRef(prefix)(setting.withPos(rhs.pos)) if (prefixRes.hasErrors) prefixRes - else prefixRes.value.assign(tp.symbol, rhsRes.value, env.heap, rhs.pos) + else prefixRes.value.assign(tp.symbol, rhsRes.value)(setting.withPos(rhs.pos)) } case sel @ Select(qual, _) => - val prefixRes = apply(qual, env) - prefixRes.value.assign(sel.symbol, rhsRes.value, env.heap, rhs.pos) + val prefixRes = apply(qual) + prefixRes.value.assign(sel.symbol, rhsRes.value)(setting.withPos(rhs.pos)) } } /** Check a parent call */ - def checkInit(tp: Type, init: Symbol, argss: List[List[Tree]], env: Env, obj: ObjectValue, pos: Position)(implicit ctx: Context): Res = { + def checkInit(tp: Type, init: Symbol, argss: List[List[Tree]], obj: ObjectValue)(implicit setting: Setting): Res = { if (!init.exists) return Res() val cls = init.owner.asClass @@ -188,7 +188,7 @@ class Analyzer extends Indexer { analyzer => // setup constructor params var effs = Vector.empty[Effect] val argValues = args.map { arg => - val res = apply(arg, env) + val res = apply(arg) effs = effs ++ res.effects res.value } @@ -201,29 +201,30 @@ class Analyzer extends Indexer { analyzer => } val prefix = toPrefix(tp) - if (prefix == NoPrefix) env.init(init, argValues, args.map(_.pos), pos, obj, this) + if (prefix == NoPrefix) setting.env.init(init, argValues, args.map(_.pos), obj) else { - val prefixRes = checkRef(prefix, env, pos) + val prefixRes = checkRef(prefix) if (prefixRes.hasErrors) return prefixRes - prefixRes.value.init(init, argValues, args.map(_.pos), pos, obj, env.heap, this) + prefixRes.value.init(init, argValues, args.map(_.pos), obj) } } - def checkParents(cls: ClassSymbol, parents: List[Tree], env: Env, obj: ObjectValue)(implicit ctx: Context): Res = { + def checkParents(cls: ClassSymbol, parents: List[Tree], obj: ObjectValue)(implicit setting: Setting): Res = { if (cls.is(Trait)) return Res() def blockInit(stats: List[Tree], parent: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]]): Res = { - val newEnv = env.fresh() - indexStats(stats, newEnv) - val res = checkStats(stats, newEnv) - res ++ checkInit(parent.tpe, init, argss, newEnv, obj, parent.pos).effects + val newEnv = setting.env.fresh() + val setting2 = setting.withEnv(newEnv).withPos(parent.pos) + indexStats(stats)(setting2) + val res = checkStats(stats)(setting2) + res ++ checkInit(parent.tpe, init, argss, obj)(setting2).effects } // first call super class, see spec 5.1 about "Template Evaluation". val res = parents.head match { case parent @ NewEx(tref, init, argss) => - checkInit(parent.tpe, init, argss, env, obj, parent.pos) + checkInit(parent.tpe, init, argss, obj)(setting.withPos(parent.pos)) case Block(stats, parent @ NewEx(tref, init, argss)) => blockInit(stats, parent, tref, init, argss) case Apply(Block(stats, parent @ NewEx(tref, init, argss)), args) => @@ -240,17 +241,17 @@ class Analyzer extends Indexer { analyzer => val parentOpt = parents.find(_.tpe.classSymbol `eq` traitCls) parentOpt match { case Some(parent @ NewEx(tref, init, argss)) => - checkInit(parent.tpe, init, argss, env, obj, parent.pos).join(acc) + checkInit(parent.tpe, init, argss, obj)(setting.withPos(cls.pos)).join(acc) case _ => val tp = obj.tp.baseType(traitCls) - checkInit(tp, traitCls.primaryConstructor, Nil, env, obj, cls.pos).join(acc) + checkInit(tp, traitCls.primaryConstructor, Nil, obj)(setting.withPos(cls.pos)).join(acc) } } } - def checkNew(tree: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]], env: Env)(implicit ctx: Context): Res = { + def checkNew(tree: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]])(implicit setting: Setting): Res = { val obj = new ObjectValue(tree.tpe, open = false) - val res = checkInit(obj.tp, init, argss, env, obj, tree.pos) + val res = checkInit(obj.tp, init, argss, obj) if (obj.slices.isEmpty) { res.copy(value = FullValue) } @@ -260,10 +261,10 @@ class Analyzer extends Indexer { analyzer => } } - def checkSuper(tree: Tree, supert: Super, env: Env)(implicit ctx: Context): Res = { + def checkSuper(tree: Tree, supert: Super)(implicit setting: Setting): Res = { val SuperType(thistpe, supertpe) = supert.tpe - val thisRef = checkRef(thistpe, env, tree.pos) - thisRef.value.select(tree.symbol, env.heap, tree.pos, isStaticDispatch = true) + val thisRef = checkRef(thistpe) + thisRef.value.select(tree.symbol, isStaticDispatch = true) } object NewEx { @@ -282,35 +283,39 @@ class Analyzer extends Indexer { analyzer => } } - def apply(tree: Tree, env: Env)(implicit ctx: Context): Res = trace("checking " + tree.show, env)(tree match { + def apply(tree: Tree)(implicit setting: Setting): Res = trace("checking " + tree.show) { + doApply(tree)(setting.withPos(tree.pos)) + } + + def doApply(tree: Tree)(implicit setting: Setting): Res = tree match { case vdef : ValDef if !vdef.symbol.is(Lazy) && !vdef.rhs.isEmpty => - checkValDef(vdef, env) + checkValDef(vdef) case _: DefTree => // ignore, definitions, already indexed Res() case Closure(_, meth, _) => - checkClosure(meth.symbol, tree, env) + checkClosure(meth.symbol, tree) case tree: Ident if tree.symbol.isTerm => - checkRef(tree.tpe, env, tree.pos) + checkRef(tree.tpe) case tree: This => - checkRef(tree.tpe, env, tree.pos) + checkRef(tree.tpe) case tree @ Select(supert: Super, _) => - checkSuper(tree, supert, env) + checkSuper(tree, supert) case tree: Select if tree.symbol.isTerm => - checkSelect(tree, env) + checkSelect(tree) case tree: If => - checkIf(tree, env) + checkIf(tree) case tree @ NewEx(tref, init, argss) => // must before Apply - checkNew(tree, tref, init, argss, env) + checkNew(tree, tref, init, argss) case tree: Apply => val (fn, targs, vargss) = decomposeCall(tree) - checkApply(tree, fn, vargss, env) + checkApply(tree, fn, vargss) case tree @ Assign(lhs, rhs) => - checkAssign(lhs, rhs, env) + checkAssign(lhs, rhs) case tree: Block => - checkBlock(tree, env) + checkBlock(tree) case Typed(expr, tpt) if !tpt.tpe.hasAnnotation(defn.UncheckedAnnot) => - apply(expr, env) + apply(expr) case _ => Res() - }) + } } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 6ba98630b6d4..da171dbf2a55 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -104,29 +104,30 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => // current class env needs special setup val root = Heap.createRootEnv + val setting = Setting(root, cls.pos, ctx, analyzer) val obj = new ObjectValue(tp = cls.typeRef, open = !cls.is(Final) && !cls.isAnonymousClass) // enhancement possible to check if there are actual children // and whether children are possible in other modules. // for recursive usage root.addClassDef(cls, tmpl) - indexOuter(cls, root) + indexOuter(cls)(setting) // init check val constr = tmpl.constr val values = constr.vparamss.flatten.map { param => param.tpe.widen.value } val poss = constr.vparamss.flatten.map(_.pos) - val res = root.init(constr.symbol, values, poss, cls.pos, obj, analyzer) + val res = root.init(constr.symbol, values, poss, obj)(setting) val sliceValue = obj.slices(cls).asInstanceOf[SliceValue] val slice = root.heap(sliceValue.id).asSlice res.effects.foreach(_.report) - // init check: try commit early - if (obj.open) initCheck(cls, obj, tmpl, root.heap) - if (obj.open) obj.annotate(cls) + + // init check: try commit early + if (obj.open) initCheck(cls, obj, tmpl)(setting) } def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { @@ -136,16 +137,16 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkMethod(ddef: tpd.DefDef): Unit = { val sym = ddef.symbol - if (!sym.isPartial && !sym.isCalledAbove(cls)) return + if (!sym.isEffectivePartial) return val root = Heap.createRootEnv - val heap = root.heap - indexOuter(cls, root) + val setting: Setting = Setting(root, sym.pos, ctx, analyzer) + indexOuter(cls)(setting) if (sym.isPartial) root.add(cls, BlankValue) else root.add(cls, PartialValue) - val value = analyzer.methodValue(ddef, root) - val res = value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + val value = analyzer.methodValue(ddef)(setting) + val res = value.apply(i => FullValue, i => NoPosition)(setting) if (res.hasErrors) { ctx.warning("Calling the method during initialization causes errors", sym.pos) @@ -158,23 +159,23 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkLazy(vdef: tpd.ValDef): Unit = { val sym = vdef.symbol - if (!sym.isPartial && !sym.isCalledAbove(cls)) return + if (!sym.isEffectivePartial) return val root = Heap.createRootEnv - val heap = root.heap - indexOuter(cls, root) + val setting: Setting = Setting(root, sym.pos, ctx, analyzer) + indexOuter(cls)(setting) if (sym.isPartial) root.add(cls, BlankValue) else root.add(cls, PartialValue) - val value = analyzer.lazyValue(vdef, root) - val res = value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + val value = analyzer.lazyValue(vdef)(setting) + val res = value.apply(i => FullValue, i => NoPosition)(setting) if (res.hasErrors) { ctx.warning("Forcing partial lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { - val value = res.value.widen(heap, sym.pos) + val value = res.value.widen()(setting) if (value != FullValue) ctx.warning("Partial lazy value must return a full value", sym.pos) } } @@ -188,43 +189,47 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } } - def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template, heap: Heap)(implicit ctx: Context) = { + def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template)(implicit setting: Setting) = { def checkMethod(sym: Symbol): Unit = { - var res = obj.select(sym, heap, sym.pos, isStaticDispatch = true) + if (!sym.isEffectiveInit) return + + var res = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)) if (!sym.info.isParameterless) - res = res.value.apply(i => FullValue, i => NoPosition, sym.pos, heap) + res = res.value.apply(i => FullValue, i => NoPosition) if (res.hasErrors) { - ctx.warning("Calling the init method causes errors", sym.pos) + setting.ctx.warning("Calling the init method causes errors", sym.pos) res.effects.foreach(_.report) } else if (res.value != FullValue) { - ctx.warning("An init method must return a full value", sym.pos) + setting.ctx.warning("An init method must return a full value", sym.pos) } + + obj.clearDynamicCalls() } def checkLazy(sym: Symbol): Unit = { - if (!sym.isInit) return + if (!sym.isEffectiveInit) return - val res = obj.select(sym, heap, sym.pos, isStaticDispatch = true) + val res = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)) if (res.hasErrors) { - ctx.warning("Forcing init lazy value causes errors", sym.pos) + setting.ctx.warning("Forcing init lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { - val value = res.value.widen(heap, sym.pos) - if (value != FullValue) ctx.warning("Init lazy value must return a full value", sym.pos) + val value = res.value.widen() + if (value != FullValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } + + obj.clearDynamicCalls() } def checkValDef(sym: Symbol): Unit = { if (sym.is(Flags.PrivateOrLocal)) return - val expected: OpaqueValue = - if (sym.isCalledAbove(cls)) FullValue - else sym.value + val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen() + if (actual < FullValue) sym.addAnnotation(Annotations.ConcreteAnnotation(New(defn.FilledAnnotType, Nil))) - val actual = obj.select(sym, heap, sym.pos, isStaticDispatch = true).value.widen(heap, sym.pos) - if (actual < expected) ctx.warning(s"Found = $actual, expected = $expected" , sym.pos) + obj.clearDynamicCalls() } tmpl.body.foreach { @@ -239,18 +244,18 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } - def indexOuter(cls: ClassSymbol, env: Env)(implicit ctx: Context) = { + def indexOuter(cls: ClassSymbol)(implicit setting: Setting) = { def recur(cls: Symbol, maxValue: OpaqueValue): Unit = if (cls.owner.exists) { val outerValue = cls.value val enclosingCls = cls.owner.enclosingClass if (!cls.owner.isClass || maxValue == FullValue) { - env.add(enclosingCls, FullValue) + setting.env.add(enclosingCls, FullValue) recur(enclosingCls, FullValue) } else { val meet = outerValue.meet(maxValue) - env.add(enclosingCls, meet) + setting.env.add(enclosingCls, meet) recur(enclosingCls, meet) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 017c0f3fe8a8..c36f74a2a25b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -44,7 +44,7 @@ case class Res(var effects: Effects = Vector.empty, var value: Value = FullValue value = res2.value.join(value) ) - def show(setting: ShowSetting)(implicit ctx: Context): String = + def show(implicit setting: ShowSetting): String = s"""~Res( ~| effects = ${if (effects.isEmpty) "()" else effects.mkString("\n| - ", "\n| - ", "")} ~| value = ${value.show(setting)} diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 52bfc32cddd1..e63f4dc2dcbd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -178,33 +178,33 @@ class Env(outerId: Int) extends HeapEntry { } /** Assign to a local variable, i.e. TermRef with NoPrefix */ - def assign(sym: Symbol, value: Value, pos: Position)(implicit ctx: Context): Res = + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = if (this.contains(sym)) { this(sym) = value Res() } - else if (value.widen(this.heap, pos) != FullValue) // leak assign - Res(effects = Vector(Generic("Cannot leak an object under initialization", pos))) + else if (value.widen() != FullValue) // leak assign + Res(effects = Vector(Generic("Cannot leak an object under initialization", setting.pos))) else Res() /** Select a local variable, i.e. TermRef with NoPrefix */ - def select(sym: Symbol, pos: Position)(implicit ctx: Context): Res = + def select(sym: Symbol)(implicit setting: Setting): Res = if (this.contains(sym)) { val value = this(sym) if (sym.is(Flags.Lazy)) { if (value.isInstanceOf[LazyValue]) { - val res = value(Nil, Nil, pos, this.heap) + val res = value(Nil, Nil) this(sym) = res.value - if (res.hasErrors) Res(effects = Vector(Force(sym, res.effects, pos))) + if (res.hasErrors) Res(effects = Vector(Force(sym, res.effects, setting.pos))) else Res(value = res.value) } else Res(value = value) } else if (sym.is(Flags.Method)) { if (sym.info.isInstanceOf[ExprType]) { // parameter-less call - value(Nil, Nil, pos, this.heap) + value(Nil, Nil) } else Res(value = value) } @@ -219,19 +219,19 @@ class Env(outerId: Int) extends HeapEntry { // How do we know the class/method/field does not capture/use a partial/filled outer? // If method/field exist, then the outer class beyond the method/field is full, // i.e. external methods/fields/classes are always safe. - FullValue.select(sym, this.heap, pos) + FullValue.select(sym) } - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass if (this.containsClass(cls)) { val tmpl = this.getClassDef(cls) - indexer.init(constr, tmpl, values, argPos, pos, obj, this) + setting.indexer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(this)) } - else FullValue.init(constr, values, argPos, pos, obj, heap, indexer) + else FullValue.init(constr, values, argPos, obj) } - def show(setting: ShowSetting)(implicit ctx: Context): String = { + def show(implicit setting: ShowSetting): String = { def members = _syms.map { case (k, v) => k.show + " ->" + setting.indent(v.show(setting), tabs = 2) }.mkString("\n") (if (outerId > 0) outer.show(setting) + "\n" else "") ++ s"-------------- $id($outerId) ---------------------\n${members}" @@ -295,7 +295,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo case _ => false } - def show(setting: ShowSetting)(implicit ctx: Context): String = { + def show(implicit setting: ShowSetting): String = { if (setting.printed.contains(id)) return "id: " + id setting.printed += id @@ -306,7 +306,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo s"\n id: $id($innerEnvId)\n${setting.indent(members, tabs = 1)}" } - def widen(implicit ctx: Context): OpaqueValue = { + def widen(implicit setting: Setting): OpaqueValue = { def isPartialOrFilled(value: Value): Boolean = value == PartialValue || value == FilledValue @@ -316,7 +316,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo // check outer val owner = cls.owner if (!owner.isClass) FullValue - else innerEnv(owner).widen(heap, NoPosition) + else innerEnv(owner).widen() } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index d180ca00808b..a69852abde48 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -27,53 +27,61 @@ import collection.mutable trait Indexer { self: Analyzer => import tpd._ - def methodValue(ddef: DefDef, env: Env)(implicit ctx: Context): FunctionValue = + def methodValue(ddef: DefDef)(implicit setting: Setting): FunctionValue = new FunctionValue { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = + def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { + // TODO: why implicit conversion does not work + implicit val ctx: Context = setting2.ctx if (isChecking(ddef.symbol)) { // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. debug(s"recursive call of ${ddef.symbol} found") Res() } else { - val env2 = env.fresh(heap) + val env2 = setting.env.fresh(setting2.heap) + val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)).withEnv(env2) ddef.vparamss.flatten.zipWithIndex.foreach { case (param: ValDef, index) => env2.add(param.symbol, value = values(index)) } - val res = checking(ddef.symbol) { self.apply(ddef.rhs, env2)(ctx.withOwner(ddef.symbol)) } - if (res.hasErrors) res.effects = Vector(Call(ddef.symbol, res.effects, pos)) + val res = checking(ddef.symbol) { self.apply(ddef.rhs)(setting3) } + if (res.hasErrors) res.effects = Vector(Call(ddef.symbol, res.effects, setting2.pos)) res } + } } - def lazyValue(vdef: ValDef, env: Env)(implicit ctx: Context): LazyValue = + def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = new LazyValue { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = + def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { + // TODO: why implicit conversion does not work + implicit val ctx: Context = setting2.ctx if (isChecking(vdef.symbol)) { // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. debug(s"recursive call of ${vdef.symbol} found") Res() } else { - val env2 = heap(env.id).asEnv - val res = checking(vdef.symbol) { self.apply(vdef.rhs, env2)(ctx.withOwner(vdef.symbol)) } - if (res.hasErrors) res.effects = Vector(Force(vdef.symbol, res.effects, pos)) + val env2 = setting2.heap(setting.env.id).asEnv + val setting3: Setting = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)).withEnv(env2) + val res = checking(vdef.symbol) { self.apply(vdef.rhs)(setting3) } + if (res.hasErrors) res.effects = Vector(Force(vdef.symbol, res.effects, setting2.pos)) res } + } } /** Index local definitions */ - def indexStats(stats: List[Tree], env: Env)(implicit ctx: Context): Unit = stats.foreach { + def indexStats(stats: List[Tree])(implicit setting: Setting): Unit = stats.foreach { case ddef: DefDef if !ddef.symbol.isConstructor => // TODO: handle secondary constructor - env.add(ddef.symbol, methodValue(ddef, env)) + setting.env.add(ddef.symbol, methodValue(ddef)) case vdef: ValDef if vdef.symbol.is(Lazy) => - env.add(vdef.symbol, lazyValue(vdef, env)) + setting.env.add(vdef.symbol, lazyValue(vdef)) case vdef: ValDef => - env.add(vdef.symbol, NoValue) + setting.env.add(vdef.symbol, NoValue) case tdef: TypeDef if tdef.isClassDef => // class has to be handled differently because of inheritance - env.addClassDef(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) + setting.env.addClassDef(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) case _ => } @@ -81,11 +89,11 @@ trait Indexer { self: Analyzer => * * trick: use `slice` for name resolution, but `env` for method execution */ - def indexMembers(stats: List[Tree], slice: SliceRep)(implicit ctx: Context): Unit = stats.foreach { + def indexMembers(stats: List[Tree], slice: SliceRep)(implicit setting: Setting): Unit = stats.foreach { case ddef: DefDef => - slice.add(ddef.symbol, methodValue(ddef, slice.innerEnv)) + slice.add(ddef.symbol, methodValue(ddef)(setting.withEnv(slice.innerEnv))) case vdef: ValDef if vdef.symbol.is(Lazy) => - slice.add(vdef.symbol, lazyValue(vdef, slice.innerEnv)) + slice.add(vdef.symbol, lazyValue(vdef)(setting.withEnv(slice.innerEnv))) case vdef: ValDef => val value = if (vdef.symbol.isInit || vdef.symbol.is(Deferred)) FullValue else NoValue slice.add(vdef.symbol, value) @@ -95,7 +103,7 @@ trait Indexer { self: Analyzer => case _ => } - def init(constr: Symbol, tmpl: Template, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, env: Env)(implicit ctx: Context): Res = { + def init(constr: Symbol, tmpl: Template, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass if (isChecking(cls)) { @@ -103,7 +111,7 @@ trait Indexer { self: Analyzer => Res() } else checking(cls) { - val slice = env.newSlice(cls) + val slice = setting.env.newSlice(cls) obj.add(cls, new SliceValue(slice.id)) // The outer of parents are set (but not recursively) @@ -127,11 +135,12 @@ trait Indexer { self: Analyzer => slice.innerEnv.add(cls, obj) // call parent constructor - val res = checkParents(cls, tmpl.parents, slice.innerEnv, obj)(ctx.withOwner(cls.owner)) + val setting2 = setting.withCtx(setting.ctx.withOwner(cls.owner)).withEnv(slice.innerEnv) + val res = checkParents(cls, tmpl.parents, obj)(setting2) if (res.hasErrors) return res // check current class body - res ++= checkStats(tmpl.body, slice.innerEnv)(ctx.withOwner(cls)).effects + res ++= checkStats(tmpl.body)(setting2).effects res } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala new file mode 100644 index 000000000000..da4679aeed92 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts.Context +import util.Positions._ +import config.Printers.init.{ println => debug } + + +case class Setting( + env: Env, + pos: Position, + ctx: Context, + indexer: Indexer, + allowDynamic: Boolean = true) { + def strict: Setting = copy(allowDynamic = false) + def heap: Heap = env.heap + def withPos(position: Position) = copy(pos = position) + def withEnv(ienv: Env) = copy(env = ienv) + def withCtx(ictx: Context) = copy(ctx = ictx) + def freshHeap: Setting = { + val id = env.id + val heap2 = env.heap.clone + val env2 = heap2(id) + copy(env = env2.asEnv) + } + + def showSetting = ShowSetting(heap, ctx) + } diff --git a/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala b/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala index f68b6771c925..b170fb669d26 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/ShowSetting.scala @@ -2,9 +2,11 @@ package dotty.tools.dotc package transform package init +import core._ +import Contexts.Context import collection.mutable -case class ShowSetting(heap: Heap, printed: mutable.Set[Int] = mutable.Set()) { +case class ShowSetting(heap: Heap, ctx: Context, printed: mutable.Set[Int] = mutable.Set()) { def indent(content: String, tabs: Int = 1): String = ShowSetting.pad(content, tabs, padFirst = true) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index ea124bee5539..e5998dc8920d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -22,23 +22,23 @@ import collection.mutable //======================================= object Value { - def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(FullValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) - if (value.widen(heap, pos) < tp.value) + if (value.widen() < tp.value) return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) } Res() } - def defaultFunctionValue(methSym: Symbol)(implicit ctx: Context): Value = { + def defaultFunctionValue(methSym: Symbol)(implicit setting: Setting): Value = { assert(methSym.is(Flags.Method)) if (methSym.info.paramNamess.isEmpty) FullValue else new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val paramInfos = methSym.info.paramInfoss.flatten - checkParams(methSym, paramInfos, values, argPos, pos, heap) + checkParams(methSym, paramInfos, values, argPos) } } } @@ -47,18 +47,18 @@ object Value { /** Abstract values in analysis */ sealed trait Value { /** Select a member on a value */ - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean = false)(implicit ctx: Context): Res + def select(sym: Symbol, isStaticDispatch: Boolean = false)(implicit setting: Setting): Res /** Assign on a value */ - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res /** Index an inner class with current value as the immediate outer */ - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res /** Apply a method or function to the provided arguments */ - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res - def show(setting: ShowSetting)(implicit ctx: Context): String + def show(implicit setting: ShowSetting): String /** Join two values * @@ -91,47 +91,47 @@ sealed trait Value { * * Widening is needed at analysis boundary. */ - def widen(heap: Heap, pos: Position, handler: Vector[Effect] => Unit = effs => ())(implicit ctx: Context): OpaqueValue = { - def recur(value: Value, heap: Heap): OpaqueValue = value match { + def widen(handler: Vector[Effect] => Unit = effs => ())(implicit setting: Setting): OpaqueValue = { + def recur(value: Value)(implicit setting: Setting): OpaqueValue = value match { case ov: OpaqueValue => ov case fv: FunctionValue => - val testHeap = heap.clone - val res = fv(i => FullValue, i => NoPosition, pos, testHeap) + val setting2 = setting.freshHeap + val res = fv(i => FullValue, i => NoPosition)(setting2) if (res.hasErrors) { handler(res.effects) FilledValue } - else recur(res.value, testHeap) + else recur(res.value)(setting2) case sv: SliceValue => - heap(sv.id).asSlice.widen + setting.heap(sv.id).asSlice.widen case ov: ObjectValue => if (ov.open) PartialValue else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => if (acc != FullValue) return FilledValue - recur(v, heap).join(acc) + recur(v).join(acc) } case UnionValue(vs) => vs.foldLeft(FullValue: OpaqueValue) { (acc, v) => if (v == PartialValue || acc == PartialValue) return PartialValue - else acc.join(recur(v, heap)) + else acc.join(recur(v)) } // case NoValue => NoValue case _ => // impossible ??? } - recur(this, heap) + recur(this) } } /** The value is absent */ object NoValue extends Value { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = ??? - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = ??? + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? - def show(setting: ShowSetting)(implicit ctx: Context): String = "NoValue" + def show(implicit setting: ShowSetting): String = "NoValue" } /** A single value, instead of a union value */ @@ -139,41 +139,41 @@ sealed trait SingleValue extends Value /** Union of values */ case class UnionValue(val values: Set[SingleValue]) extends Value { - def apply(args: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(args: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { values.foldLeft(Res()) { (acc, value) => - value.apply(args, argPos, pos, heap).join(acc) + value.apply(args, argPos).join(acc) } } - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { values.foldLeft(Res()) { (acc, value) => - value.select(sym, heap, pos, isStaticDispatch).join(acc) + value.select(sym, isStaticDispatch).join(acc) } } - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { values.foldLeft(Res()) { (acc, value) => - value.assign(sym, value, heap, pos).join(acc) + value.assign(sym, value).join(acc) } } - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { values.foldLeft(Res()) { (acc, value) => - value.init(constr, values, argPos, pos, obj, heap, indexer).join(acc) + value.init(constr, values, argPos, obj).join(acc) } } def +(value: SingleValue): UnionValue = UnionValue(values + value) def ++(uv: UnionValue): UnionValue = UnionValue(values ++ uv.values) - def show(setting: ShowSetting)(implicit ctx: Context): String = + def show(implicit setting: ShowSetting): String = "Or{" + setting.indent(values.map(v => v.show(setting)).mkString(", ")) + "}" } /** Values that are subject to type checking rather than analysis */ abstract sealed class OpaqueValue extends SingleValue { // not supported - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? def <(that: OpaqueValue): Boolean = (this, that) match { case (FullValue, _) => false @@ -190,28 +190,28 @@ abstract sealed class OpaqueValue extends SingleValue { } object FullValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) else Res() - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = - if (value.widen(heap, pos) != FullValue) - Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", pos))) + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = + if (value.widen() != FullValue) + Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", setting.pos))) else Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(cls, paramInfos, values, argPos, pos, heap) + val res = Value.checkParams(cls, paramInfos, values, argPos) if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(FullValue)) - if (args.exists(_.widen(heap, pos) < FullValue)) obj.add(cls, FilledValue) + if (args.exists(_.widen() < FullValue)) obj.add(cls, FilledValue) Res() } - def show(setting: ShowSetting)(implicit ctx: Context): String = "Full" + def show(implicit setting: ShowSetting): String = "Full" override def toString = "full value" } @@ -230,42 +230,42 @@ object FullValue extends OpaqueValue { * class C extends A with B(20) */ object BlankValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) if (sym.is(Flags.Method)) { if (!sym.isPartial && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@partial` in order to be called", pos) + res += Generic(s"The $sym should be marked as `@partial` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { if (!sym.isPartial) - res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", pos) + res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", setting.pos) } else if (sym.isClass) { if (!sym.isPartial) - res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", pos) + res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", setting.pos) } else { // field select - res += Generic(s"The $sym may not be initialized", pos) + res += Generic(s"The $sym may not be initialized", setting.pos) } res } /** assign to partial is always fine? */ - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = Res() + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + val res = Value.checkParams(constr.owner, paramInfos, values, argPos) if (res.hasErrors) return res val cls = constr.owner.asClass if (!cls.isPartial) { - res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", pos) + res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", setting.pos) res.value = FullValue return res } @@ -275,7 +275,7 @@ object BlankValue extends OpaqueValue { Res() } - def show(setting: ShowSetting)(implicit ctx: Context): String = "Partial" + def show(implicit setting: ShowSetting): String = "Partial" override def toString = "blank value" } @@ -285,43 +285,43 @@ object BlankValue extends OpaqueValue { * TODO: rename to `raw` */ object PartialValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) if (sym.is(Flags.Method)) { if (!sym.isPartial && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@partial` in order to be called", pos) + res += Generic(s"The $sym should be marked as `@partial` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { if (!sym.isPartial) - res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", pos) + res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", setting.pos) } else if (sym.isClass) { if (!sym.isPartial) - res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", pos) + res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", setting.pos) } else { // field select if (!sym.isClassParam) - res += Generic(s"The $sym may not be initialized", pos) + res += Generic(s"The $sym may not be initialized", setting.pos) } res } /** assign to partial is always fine? */ - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = Res() + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + val res = Value.checkParams(constr.owner, paramInfos, values, argPos) if (res.hasErrors) return res val cls = constr.owner.asClass if (!cls.isPartial) { - res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", pos) + res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", setting.pos) res.value = FullValue return res } @@ -331,23 +331,23 @@ object PartialValue extends OpaqueValue { Res() } - def show(setting: ShowSetting)(implicit ctx: Context): String = "Partial" + def show(implicit setting: ShowSetting): String = "Partial" override def toString = "partial value" } object FilledValue extends OpaqueValue { - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { - if (!sym.isPartial && !sym.isInit && !sym.isCalledIn(sym.owner.asClass) && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@init` in order to be called", pos) + if (!sym.isPartial && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@init` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } - else if (sym.is(Flags.Lazy) && !sym.isCalledIn(sym.owner.asClass)) { + else if (sym.is(Flags.Lazy) && !sym.isEffectiveInit) { if (!sym.isPartial && !sym.isFilled) - res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", pos) + res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) res.value = sym.info.value } @@ -358,19 +358,19 @@ object FilledValue extends OpaqueValue { res } - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = - if (value.widen(heap, pos) < sym.info.value) - Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", pos))) + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = + if (value.widen() < sym.info.value) + Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", setting.pos))) else Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos, pos, heap) + val res = Value.checkParams(constr.owner, paramInfos, values, argPos) if (res.hasErrors) return res val cls = constr.owner.asClass if (!cls.isPartial && !cls.isFilled) { - res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", pos) + res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) res.value = FullValue return res } @@ -380,27 +380,27 @@ object FilledValue extends OpaqueValue { Res() } - def show(setting: ShowSetting)(implicit ctx: Context): String = "Filled" + def show(implicit setting: ShowSetting): String = "Filled" override def toString = "filled value" } /** A function value or value of method select */ abstract class FunctionValue extends SingleValue { self => - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = sym.name match { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = sym.name match { case nme.apply | nme.lift => Res(value = this) case nme.compose => val selectedFun = new FunctionValue() { - def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(fun: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val composedFun = new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val arg = values(0) val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol - val res1 = fun(0).select(applySym, heap, pos) - val res2 = res1.value.apply(arg :: Nil, argPos, pos, heap) - val res3 = self.apply(res2.value :: Nil, argPos, pos, heap) + val res1 = fun(0).select(applySym) + val res2 = res1.value.apply(arg :: Nil, argPos) + val res3 = self.apply(res2.value :: Nil, argPos) Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) } } @@ -410,14 +410,14 @@ abstract class FunctionValue extends SingleValue { self => Res(value = selectedFun) case nme.andThen => val selectedFun = new FunctionValue() { - def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(fun: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val composedFun = new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val arg = values(0) - val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val res1 = self.apply(arg :: Nil, argPos) val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol - val res2 = fun(0).select(applySym, heap, pos) - val res3 = res2.value.apply(res2.value :: Nil, argPos, pos, heap) + val res2 = fun(0).select(applySym) + val res3 = res2.value.apply(res2.value :: Nil, argPos) Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) } } @@ -427,27 +427,27 @@ abstract class FunctionValue extends SingleValue { self => Res(value = selectedFun) case nme.applyOrElse => val selectedFun = new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val arg = values(0) val fun = values(1) - val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val res1 = self.apply(arg :: Nil, argPos) val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol - val res2 = fun.select(applySym, heap, pos) - val res3 = res2.value.apply(arg :: Nil, argPos, pos, heap) + val res2 = fun.select(applySym) + val res3 = res2.value.apply(arg :: Nil, argPos) Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) } } Res(value = selectedFun) case nme.runWith => val selectedFun = new FunctionValue() { - def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(fun: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val composedFun = new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val arg = values(0) - val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val res1 = self.apply(arg :: Nil, argPos) val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol - val res2 = fun(0).select(applySym, heap, pos) - val res3 = res2.value.apply(res2.value :: Nil, argPos, pos, heap) + val res2 = fun(0).select(applySym) + val res3 = res2.value.apply(res2.value :: Nil, argPos) Res(value = FullValue, effects = res1.effects ++ res2.effects ++ res3.effects) } } @@ -457,14 +457,14 @@ abstract class FunctionValue extends SingleValue { self => Res(value = selectedFun) case nme.orElse => val selectedFun = new FunctionValue() { - def apply(fun: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(fun: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val composedFun = new FunctionValue() { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val arg = values(0) - val res1 = self.apply(arg :: Nil, argPos, pos, heap) + val res1 = self.apply(arg :: Nil, argPos) val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol - val res2 = fun(0).select(applySym, heap, pos) - val res3 = res2.value.apply(arg :: Nil, argPos, pos, heap) + val res2 = fun(0).select(applySym) + val res3 = res2.value.apply(arg :: Nil, argPos) Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) } } @@ -473,24 +473,24 @@ abstract class FunctionValue extends SingleValue { self => } Res(value = selectedFun) case _ => - FullValue.select(sym, heap, pos) + FullValue.select(sym) } /** not supported */ - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? - def show(setting: ShowSetting)(implicit ctx: Context): String = toString + def show(implicit setting: ShowSetting): String = toString override def toString: String = "Function@" + hashCode def join(that: FunctionValue): FunctionValue = new FunctionValue { - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = { - val heap2 = heap.clone - val res1 = self(values, argPos, pos, heap) - val res2 = that(values, argPos, pos, heap2) - heap.join(heap2) + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { + val setting2 = setting.freshHeap + val res1 = self(values, argPos) + val res2 = that(values, argPos)(setting2) + setting.heap.join(setting2.heap) res1.join(res2) } } @@ -500,11 +500,11 @@ abstract class FunctionValue extends SingleValue { self => /** A lazy value */ abstract class LazyValue extends SingleValue { // not supported - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = ??? - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = ??? - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = ??? + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = ??? + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? - def show(setting: ShowSetting)(implicit ctx: Context): String = toString + def show(implicit setting: ShowSetting): String = toString override def toString: String = "LazyValue@" + hashCode } @@ -512,15 +512,15 @@ abstract class LazyValue extends SingleValue { /** A slice of an object */ class SliceValue(val id: Int) extends SingleValue { /** not supported, impossible to apply an object value */ - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { - val slice = heap(id).asSlice + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { + val slice = setting.heap(id).asSlice val value = slice(sym) if (sym.is(Flags.Lazy)) { if (value.isInstanceOf[LazyValue]) { - val res = value(Nil, Nil, pos, heap) + val res = value(Nil, Nil) slice(sym) = res.value res } @@ -528,14 +528,14 @@ class SliceValue(val id: Int) extends SingleValue { } else if (sym.is(Flags.Method)) { if (sym.info.isParameterless) { // parameter-less call - value(Nil, Nil, pos, heap) + value(Nil, Nil) } else Res(value = value) } else { if (value == NoValue) { if (sym.info.isInstanceOf[ConstantType]) Res() - else Res(effects = Vector(Uninit(sym, pos))) + else Res(effects = Vector(Uninit(sym, setting.pos))) } else { Res(value = value) @@ -543,17 +543,17 @@ class SliceValue(val id: Int) extends SingleValue { } } - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { - val slice = heap(id).asSlice + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { + val slice = setting.heap(id).asSlice slice(sym) = value Res() } - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass - val slice = heap(id).asSlice + val slice = setting.heap(id).asSlice val tmpl = slice.classInfos(cls) - indexer.init(constr, tmpl, values, argPos, pos, obj, slice.innerEnv) + setting.indexer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) } override def hashCode = id @@ -563,7 +563,7 @@ class SliceValue(val id: Int) extends SingleValue { case _ => false } - def show(setting: ShowSetting)(implicit ctx: Context): String = setting.heap(id).asSlice.show(setting) + def show(implicit setting: ShowSetting): String = setting.heap(id).asSlice.show(setting) } class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { @@ -573,6 +573,9 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { private var _dynamicCalls: Set[Symbol] = Set.empty def dynamicCalls: Set[Symbol] = _dynamicCalls + def clearDynamicCalls(): Unit = { + _dynamicCalls = Set.empty + } def add(cls: ClassSymbol, value: Value) = { if (slices.contains(cls)) { @@ -591,67 +594,67 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } /** not supported, impossible to apply an object value */ - def apply(values: Int => Value, argPos: Int => Position, pos: Position, heap: Heap)(implicit ctx: Context): Res = ??? + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? - def select(sym: Symbol, heap: Heap, pos: Position, isStaticDispatch: Boolean)(implicit ctx: Context): Res = { + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val target = if (isStaticDispatch) sym else resolve(sym) // select on self type if (!target.exists) { if (sym.owner.is(Flags.Trait)) - return PartialValue.select(sym, heap, pos) + return PartialValue.select(sym) else - return FilledValue.select(sym, heap, pos) + return FilledValue.select(sym) } - if (this.widen(heap, pos) == FullValue) return FullValue.select(sym, heap, pos) + if (this.widen() == FullValue) return FullValue.select(sym) // remember dynamic calls - if (!isStaticDispatch && !target.isEffectivelyFinal) { + if (!isStaticDispatch && !target.isEffectivelyFinal && !target.isEffectiveInit) { _dynamicCalls = _dynamicCalls + target } val cls = target.owner.asClass if (slices.contains(cls)) { - slices(cls).select(target, heap, pos) + slices(cls).select(target) } else { // select on unknown super assert(target.isDefinedOn(tp)) - FilledValue.select(target, heap, pos) + FilledValue.select(target) } } - def assign(sym: Symbol, value: Value, heap: Heap, pos: Position)(implicit ctx: Context): Res = { + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { val target = resolve(sym) // select on self type - if (!target.exists) return PartialValue.assign(sym, value, heap, pos) + if (!target.exists) return PartialValue.assign(sym, value) val cls = target.owner.asClass if (slices.contains(cls)) { - slices(cls).assign(target, value, heap, pos) + slices(cls).assign(target, value) } else { // select on unknown super assert(target.isDefinedOn(tp)) - FilledValue.assign(target, value, heap, pos) + FilledValue.assign(target, value) } } - def init(constr: Symbol, values: List[Value], argPos: List[Position], pos: Position, obj: ObjectValue, heap: Heap, indexer: Indexer)(implicit ctx: Context): Res = { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass val outerCls = cls.owner.asClass if (slices.contains(outerCls)) { - slices(outerCls).init(constr, values, argPos, pos, obj, heap, indexer) + slices(outerCls).init(constr, values, argPos, obj) } else { val value = if (cls.isDefinedOn(tp)) FilledValue else PartialValue - value.init(constr, values, argPos, pos, obj, heap, indexer) + value.init(constr, values, argPos, obj) } } - def show(setting: ShowSetting)(implicit ctx: Context): String = { + def show(implicit setting: ShowSetting): String = { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") "Object {\n" + setting.indent(body) + "\n}" } @@ -661,5 +664,6 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { debug(s"$sym used during initialization of $cls") cls.addAnnotation(Annotation.Call(sym)) } + _dynamicCalls = Set.empty } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 54c4f5bdfe75..0c6028d85f28 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -45,6 +45,12 @@ package object init { def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) + def isEffectivePartial(implicit ctx: Context) = + sym.isPartial || sym.isCalledAbove(sym.owner.asClass) + + def isEffectiveInit(implicit ctx: Context) = + !sym.isEffectivePartial && + (sym.isInit || sym.isCalledIn(sym.owner.asClass) || sym.allOverriddenSymbols.exists(_.isInit)) def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = calledSymsIn(cls).exists(_ == sym) || sym.allOverriddenSymbols.exists(_.isCalledIn(cls)) @@ -71,4 +77,7 @@ package object init { def isField(implicit ctx: Context) = sym.isTerm && sym.is(AnyFlags, butNot = Method | Lazy | Deferred) } + + implicit def setting2ctx(implicit s: Setting): Context = s.ctx + implicit def showSetting2ctx(implicit s: ShowSetting): Context = s.ctx } \ No newline at end of file From 3143956e63bc00d11f1b0b9bc10811978e4c9f5d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 06:31:05 +0200 Subject: [PATCH 10/83] Don't make methods called in parent callable in child --- .../dotty/tools/dotc/transform/init/Analyzer.scala | 4 ++-- .../dotty/tools/dotc/transform/init/Checker.scala | 14 +++++++------- .../dotty/tools/dotc/transform/init/Indexer.scala | 2 +- .../dotty/tools/dotc/transform/init/Values.scala | 9 ++++++--- .../dotty/tools/dotc/transform/init/package.scala | 2 +- tests/neg-custom-args/safe-init/override10.scala | 11 +++++++++++ 6 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override10.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 9e60929aa4d9..d062b4820846 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -241,10 +241,10 @@ class Analyzer extends Indexer { analyzer => val parentOpt = parents.find(_.tpe.classSymbol `eq` traitCls) parentOpt match { case Some(parent @ NewEx(tref, init, argss)) => - checkInit(parent.tpe, init, argss, obj)(setting.withPos(cls.pos)).join(acc) + checkInit(parent.tpe, init, argss, obj).join(acc) case _ => val tp = obj.tp.baseType(traitCls) - checkInit(tp, traitCls.primaryConstructor, Nil, obj)(setting.withPos(cls.pos)).join(acc) + checkInit(tp, traitCls.primaryConstructor, Nil, obj).join(acc) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index da171dbf2a55..2199111b0f88 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -145,8 +145,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.isPartial) root.add(cls, BlankValue) else root.add(cls, PartialValue) - val value = analyzer.methodValue(ddef)(setting) - val res = value.apply(i => FullValue, i => NoPosition)(setting) + val value = analyzer.methodValue(ddef)(setting.strict) + val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) if (res.hasErrors) { ctx.warning("Calling the method during initialization causes errors", sym.pos) @@ -167,15 +167,15 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.isPartial) root.add(cls, BlankValue) else root.add(cls, PartialValue) - val value = analyzer.lazyValue(vdef)(setting) - val res = value.apply(i => FullValue, i => NoPosition)(setting) + val value = analyzer.lazyValue(vdef)(setting.strict) + val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) if (res.hasErrors) { ctx.warning("Forcing partial lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { - val value = res.value.widen()(setting) + val value = res.value.widen()(setting.strict) if (value != FullValue) ctx.warning("Partial lazy value must return a full value", sym.pos) } } @@ -216,7 +216,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => res.effects.foreach(_.report) } else { - val value = res.value.widen() + val value = res.value.widen()(setting.strict) if (value != FullValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } @@ -226,7 +226,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkValDef(sym: Symbol): Unit = { if (sym.is(Flags.PrivateOrLocal)) return - val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen() + val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen()(setting.strict) if (actual < FullValue) sym.addAnnotation(Annotations.ConcreteAnnotation(New(defn.FilledAnnotType, Nil))) obj.clearDynamicCalls() diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index a69852abde48..9aea37f7868d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -135,7 +135,7 @@ trait Indexer { self: Analyzer => slice.innerEnv.add(cls, obj) // call parent constructor - val setting2 = setting.withCtx(setting.ctx.withOwner(cls.owner)).withEnv(slice.innerEnv) + val setting2 = setting.withCtx(setting.ctx.withOwner(cls.owner)).withEnv(slice.innerEnv).withPos(cls.pos) val res = checkParents(cls, tmpl.parents, obj)(setting2) if (res.hasErrors) return res diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index e5998dc8920d..e5847221108b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -609,19 +609,22 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (this.widen() == FullValue) return FullValue.select(sym) + val res = Res() + // remember dynamic calls if (!isStaticDispatch && !target.isEffectivelyFinal && !target.isEffectiveInit) { - _dynamicCalls = _dynamicCalls + target + if (setting.allowDynamic) _dynamicCalls = _dynamicCalls + target + else res += Generic(s"Dynamic call to $target found", setting.pos) } val cls = target.owner.asClass if (slices.contains(cls)) { - slices(cls).select(target) + slices(cls).select(target) ++ res.effects } else { // select on unknown super assert(target.isDefinedOn(tp)) - FilledValue.select(target) + FilledValue.select(target) ++ res.effects } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 0c6028d85f28..59d80d679377 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -50,7 +50,7 @@ package object init { def isEffectiveInit(implicit ctx: Context) = !sym.isEffectivePartial && - (sym.isInit || sym.isCalledIn(sym.owner.asClass) || sym.allOverriddenSymbols.exists(_.isInit)) + (sym.isInit || sym.allOverriddenSymbols.exists(_.isInit)) def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = calledSymsIn(cls).exists(_ == sym) || sym.allOverriddenSymbols.exists(_.isCalledIn(cls)) diff --git a/tests/neg-custom-args/safe-init/override10.scala b/tests/neg-custom-args/safe-init/override10.scala new file mode 100644 index 000000000000..5f9adc33d6ed --- /dev/null +++ b/tests/neg-custom-args/safe-init/override10.scala @@ -0,0 +1,11 @@ +trait Foo { + def f: () => String = () => message + def message: String + val m = f +} + +class Bar extends Foo { + val message = "hello" + f() // error + m() // error +} \ No newline at end of file From 2eff27c46cc656b4806ef57fd582126a7d43ff46 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 07:08:52 +0200 Subject: [PATCH 11/83] Infer @init for called methods with full result --- .../src/dotty/tools/dotc/transform/init/Checker.scala | 11 +++++++---- .../src/dotty/tools/dotc/transform/init/package.scala | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 2199111b0f88..d0f34b668c7e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -191,7 +191,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template)(implicit setting: Setting) = { def checkMethod(sym: Symbol): Unit = { - if (!sym.isEffectiveInit) return + if (!sym.isEffectiveInit && !sym.isCalledIn(cls)) return var res = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)) if (!sym.info.isParameterless) @@ -200,9 +200,12 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => setting.ctx.warning("Calling the init method causes errors", sym.pos) res.effects.foreach(_.report) } - else if (res.value != FullValue) { + else if (res.value != FullValue && !sym.isCalledIn(cls)) { // effective init setting.ctx.warning("An init method must return a full value", sym.pos) } + else if (res.value == FullValue && sym.isCalledIn(cls)) { // add @init + sym.annotate(defn.InitAnnotType) + } obj.clearDynamicCalls() } @@ -227,13 +230,13 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.is(Flags.PrivateOrLocal)) return val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen()(setting.strict) - if (actual < FullValue) sym.addAnnotation(Annotations.ConcreteAnnotation(New(defn.FilledAnnotType, Nil))) + if (actual < FullValue) sym.annotate(defn.FilledAnnotType) obj.clearDynamicCalls() } tmpl.body.foreach { - case ddef: DefDef if ddef.symbol.isInit && !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => + case ddef: DefDef if !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => checkMethod(ddef.symbol) case vdef: ValDef if vdef.symbol.is(Lazy) => checkLazy(vdef.symbol) diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 59d80d679377..94f40f6fc8e3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -76,6 +76,9 @@ package object init { def isField(implicit ctx: Context) = sym.isTerm && sym.is(AnyFlags, butNot = Method | Lazy | Deferred) + + def annotate(tp: Type)(implicit ctx: Context) = + sym.addAnnotation(Annotations.ConcreteAnnotation(tpd.New(tp, Nil))) } implicit def setting2ctx(implicit s: Setting): Context = s.ctx From 902faeb609ebc663a8e831fc06b288e41a94a485 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 07:18:03 +0200 Subject: [PATCH 12/83] Record dynamic calls for @init methods --- compiler/src/dotty/tools/dotc/transform/init/Values.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index e5847221108b..0f49d49cd984 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -612,9 +612,11 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val res = Res() // remember dynamic calls - if (!isStaticDispatch && !target.isEffectivelyFinal && !target.isEffectiveInit) { - if (setting.allowDynamic) _dynamicCalls = _dynamicCalls + target - else res += Generic(s"Dynamic call to $target found", setting.pos) + if (!isStaticDispatch && !target.isEffectivelyFinal) { + if (setting.allowDynamic || target.isEffectiveInit) + _dynamicCalls = _dynamicCalls + target + else + res += Generic(s"Dynamic call to $target found", setting.pos) } val cls = target.owner.asClass From 7bffbd3ca03a71e33df864da5ba4f712eb498aec Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 07:40:29 +0200 Subject: [PATCH 13/83] Fix partial value annotation --- .../dotty/tools/dotc/transform/init/Checker.scala | 3 ++- .../dotty/tools/dotc/transform/init/Values.scala | 4 ++-- tests/neg-custom-args/safe-init/override11.scala | 13 +++++++++++++ tests/neg-custom-args/safe-init/simple-1h.scala | 2 +- tests/neg-custom-args/safe-init/simple-1i.scala | 4 ++-- 5 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override11.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index d0f34b668c7e..bceda381fd68 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -230,7 +230,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.is(Flags.PrivateOrLocal)) return val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen()(setting.strict) - if (actual < FullValue) sym.annotate(defn.FilledAnnotType) + if (actual == PartialValue) sym.annotate(defn.PartialAnnotType) + else if (actual == FilledValue) sym.annotate(defn.FilledAnnotType) obj.clearDynamicCalls() } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 0f49d49cd984..c2ab5befada8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -349,10 +349,10 @@ object FilledValue extends OpaqueValue { if (!sym.isPartial && !sym.isFilled) res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) - res.value = sym.info.value + res.value = sym.info.value.join(sym.value) } else { - res.value = sym.value + res.value = sym.info.value.join(sym.value) } res diff --git a/tests/neg-custom-args/safe-init/override11.scala b/tests/neg-custom-args/safe-init/override11.scala new file mode 100644 index 000000000000..0c6c511d7c02 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override11.scala @@ -0,0 +1,13 @@ +class B(a: Partial[A]) { + val m: A = a +} + +class A { + val b = new B(this) + println(b.m.f) // error + + val x = 10 + + @scala.annotation.init + def f: Int = x +} diff --git a/tests/neg-custom-args/safe-init/simple-1h.scala b/tests/neg-custom-args/safe-init/simple-1h.scala index fec1eda08454..a2a1a1ab753b 100644 --- a/tests/neg-custom-args/safe-init/simple-1h.scala +++ b/tests/neg-custom-args/safe-init/simple-1h.scala @@ -1,7 +1,7 @@ class Foo(n: Partial[String]) { foo(new Foo("Jack")) // recursive creation - val name: String = n // error + val name: String = n name.length // error private def foo(o: Foo) = o.name diff --git a/tests/neg-custom-args/safe-init/simple-1i.scala b/tests/neg-custom-args/safe-init/simple-1i.scala index 7f5e5e825b59..39785ca45510 100644 --- a/tests/neg-custom-args/safe-init/simple-1i.scala +++ b/tests/neg-custom-args/safe-init/simple-1i.scala @@ -1,11 +1,11 @@ class Foo(n: Partial[String]) { foo(new Foo("Jack")) // recursive creation - val name: String = n // error + val name: String = n name.length // error private def foo(o: Foo) = { def bar = o.name bar } -} \ No newline at end of file +} From 227eab745c78d9d6c60ba4b06a2b3352d6d38d28 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 10:09:31 +0200 Subject: [PATCH 14/83] Refactor code --- .../tools/dotc/transform/init/Checker.scala | 25 +++++++++++-------- .../tools/dotc/transform/init/Heap.scala | 2 +- .../tools/dotc/transform/init/Setting.scala | 2 +- .../tools/dotc/transform/init/Values.scala | 2 +- tests/neg-custom-args/safe-init/early2.scala | 2 +- .../neg-custom-args/safe-init/simple-1f.scala | 4 +-- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index bceda381fd68..5a99fa95f9de 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -190,30 +190,32 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } def initCheck(cls: ClassSymbol, obj: ObjectValue, tmpl: tpd.Template)(implicit setting: Setting) = { - def checkMethod(sym: Symbol): Unit = { + def checkMethod(ddef: tpd.DefDef)(implicit setting: Setting): Unit = { + val sym = ddef.symbol if (!sym.isEffectiveInit && !sym.isCalledIn(cls)) return - var res = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)) + var res = obj.select(sym, isStaticDispatch = true) if (!sym.info.isParameterless) res = res.value.apply(i => FullValue, i => NoPosition) if (res.hasErrors) { - setting.ctx.warning("Calling the init method causes errors", sym.pos) + setting.ctx.warning(s"Calling the init $sym causes errors", sym.pos) res.effects.foreach(_.report) } else if (res.value != FullValue && !sym.isCalledIn(cls)) { // effective init setting.ctx.warning("An init method must return a full value", sym.pos) } - else if (res.value == FullValue && sym.isCalledIn(cls)) { // add @init + else if (res.value == FullValue && sym.isCalledIn(cls)) { // de facto @init sym.annotate(defn.InitAnnotType) } obj.clearDynamicCalls() } - def checkLazy(sym: Symbol): Unit = { + def checkLazy(vdef: tpd.ValDef)(implicit setting: Setting): Unit = { + val sym = vdef.symbol if (!sym.isEffectiveInit) return - val res = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)) + val res = obj.select(sym, isStaticDispatch = true) if (res.hasErrors) { setting.ctx.warning("Forcing init lazy value causes errors", sym.pos) res.effects.foreach(_.report) @@ -226,10 +228,11 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => obj.clearDynamicCalls() } - def checkValDef(sym: Symbol): Unit = { + def checkValDef(vdef: tpd.ValDef)(implicit setting: Setting): Unit = { + val sym = vdef.symbol if (sym.is(Flags.PrivateOrLocal)) return - val actual = obj.select(sym, isStaticDispatch = true)(setting.withPos(sym.pos)).value.widen()(setting.strict) + val actual = obj.select(sym, isStaticDispatch = true).value.widen()(setting.strict) if (actual == PartialValue) sym.annotate(defn.PartialAnnotType) else if (actual == FilledValue) sym.annotate(defn.FilledAnnotType) @@ -238,11 +241,11 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => tmpl.body.foreach { case ddef: DefDef if !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => - checkMethod(ddef.symbol) + checkMethod(ddef)(setting.withPos(ddef.symbol.pos)) case vdef: ValDef if vdef.symbol.is(Lazy) => - checkLazy(vdef.symbol) + checkLazy(vdef)(setting.withPos(vdef.symbol.pos)) case vdef: ValDef => - checkValDef(vdef.symbol) + checkValDef(vdef)(setting.withPos(vdef.symbol.pos)) case _ => } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index e63f4dc2dcbd..d26abe4c94de 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -226,7 +226,7 @@ class Env(outerId: Int) extends HeapEntry { val cls = constr.owner.asClass if (this.containsClass(cls)) { val tmpl = this.getClassDef(cls) - setting.indexer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(this)) + setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(this)) } else FullValue.init(constr, values, argPos, obj) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index da4679aeed92..50355b7699b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -12,7 +12,7 @@ case class Setting( env: Env, pos: Position, ctx: Context, - indexer: Indexer, + analyzer: Analyzer, allowDynamic: Boolean = true) { def strict: Setting = copy(allowDynamic = false) def heap: Heap = env.heap diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index c2ab5befada8..634d83098bdd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -553,7 +553,7 @@ class SliceValue(val id: Int) extends SingleValue { val cls = constr.owner.asClass val slice = setting.heap(id).asSlice val tmpl = slice.classInfos(cls) - setting.indexer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) + setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) } override def hashCode = id diff --git a/tests/neg-custom-args/safe-init/early2.scala b/tests/neg-custom-args/safe-init/early2.scala index 1e58c592089a..9b512bdfa1ee 100644 --- a/tests/neg-custom-args/safe-init/early2.scala +++ b/tests/neg-custom-args/safe-init/early2.scala @@ -5,7 +5,7 @@ class Parent { class Child extends Parent { val a = 4 - def g() = foo() // error + def g() = foo() // error // error g() // error val b = 10 diff --git a/tests/neg-custom-args/safe-init/simple-1f.scala b/tests/neg-custom-args/safe-init/simple-1f.scala index 8206dc9aaeac..1d0f29ad354d 100644 --- a/tests/neg-custom-args/safe-init/simple-1f.scala +++ b/tests/neg-custom-args/safe-init/simple-1f.scala @@ -1,5 +1,5 @@ class Foo(x: Partial[String]) { - var name: String = _ // error + var name: String = _ name.size // error name = "hello, world" @@ -12,7 +12,7 @@ class Foo(x: Partial[String]) { } class Bar(x: Partial[String]) { - var name: String = x // error + var name: String = x name.size // error name = "hello, world" From f66de89c17b932a080df7dcc4ee26057dc63b4aa Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 10:40:47 +0200 Subject: [PATCH 15/83] Rename partial to raw --- .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../tools/dotc/transform/init/Checker.scala | 33 +++++---- .../tools/dotc/transform/init/Heap.scala | 10 +-- .../tools/dotc/transform/init/Values.scala | 70 +++++++++---------- .../tools/dotc/transform/init/package.scala | 14 ++-- library/src/dotty/DottyPredef.scala | 2 +- .../annotation/{partial.scala => raw.scala} | 6 +- tests/neg-custom-args/safe-init/cycle.scala | 2 +- .../safe-init/double-list2.scala | 2 +- tests/neg-custom-args/safe-init/example.scala | 10 +-- .../neg-custom-args/safe-init/function.scala | 2 +- .../neg-custom-args/safe-init/function2.scala | 2 +- tests/neg-custom-args/safe-init/if.scala | 2 +- tests/neg-custom-args/safe-init/inner1.scala | 2 +- tests/neg-custom-args/safe-init/inner2.scala | 2 +- tests/neg-custom-args/safe-init/inner3.scala | 4 +- tests/neg-custom-args/safe-init/inner4.scala | 2 +- .../safe-init/override11.scala | 2 +- .../neg-custom-args/safe-init/override4.scala | 6 +- .../neg-custom-args/safe-init/override7.scala | 2 +- .../neg-custom-args/safe-init/override8.scala | 6 +- tests/neg-custom-args/safe-init/parent3.scala | 4 +- tests/neg-custom-args/safe-init/parent7.scala | 4 +- .../safe-init/partial-select1.scala | 2 +- .../neg-custom-args/safe-init/simple-1f.scala | 4 +- .../neg-custom-args/safe-init/simple-1h.scala | 2 +- .../neg-custom-args/safe-init/simple-1i.scala | 2 +- 27 files changed, 101 insertions(+), 102 deletions(-) rename library/src/scala/annotation/{partial.scala => raw.scala} (77%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e4329c7ba9d2..5437590e2e28 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -806,8 +806,8 @@ class Definitions { def SetterMetaAnnot(implicit ctx: Context): ClassSymbol = SetterMetaAnnotType.symbol.asClass lazy val ShowAsInfixAnotType: TypeRef = ctx.requiredClassRef("scala.annotation.showAsInfix") def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass - lazy val PartialAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.partial") - def PartialAnnot(implicit ctx: Context): ClassSymbol = PartialAnnotType.symbol.asClass + lazy val RawAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.raw") + def RawAnnot(implicit ctx: Context): ClassSymbol = RawAnnotType.symbol.asClass lazy val FilledAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.filled") def FilledAnnot(implicit ctx: Context): ClassSymbol = FilledAnnotType.symbol.asClass lazy val InitAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.init") diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 5a99fa95f9de..22c979f146ab 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -29,17 +29,16 @@ object Checker { /** This transform checks initialization is safe based on data-flow analysis * - * - Partial + * - Raw * - Filled * - Full * * 1. A _full_ object is fully initialized. * 2. All fields of a _filled_ object are assigned, but the fields may refer to non-full objects. - * 3. A _partial_ object may have unassigned fields. + * 3. A _raw_ object may have unassigned fields. * * TODO: * - check default arguments of init methods - * - selection on ParamAccessors of partial value is fine if the param is not partial * - handle tailrec calls during initialization (which captures `this`) */ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => @@ -65,7 +64,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } def invalidImplementMsg(sym: Symbol) = { - val annot = if (sym.owner.is(Trait)) "partial" else "init" + val annot = if (sym.owner.is(Trait)) "raw" else "init" s"""|@scala.annotation.$annot required for ${sym.show} in ${sym.owner.show} |Because the method is called during initialization.""" .stripMargin @@ -80,7 +79,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => mbrd <- self.member(mbr.name).alternatives tp = mbr.info.asSeenFrom(self, mbr.owner) if mbrd.info.overrides(tp, matchLoosely = true) && - !mbrd.symbol.isInit && !mbrd.symbol.isPartial & + !mbrd.symbol.isInit && !mbrd.symbol.isRaw & !mbrd.symbol.isCalledAbove(cls.asClass) && !mbrd.symbol.is(Deferred) } ctx.warning(invalidImplementMsg(mbrd.symbol), cls.pos) @@ -99,8 +98,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val analyzer = new Analyzer - // partial check - partialCheck(cls, tmpl, analyzer) + // raw check + rawCheck(cls, tmpl, analyzer) // current class env needs special setup val root = Heap.createRootEnv @@ -130,20 +129,20 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (obj.open) initCheck(cls, obj, tmpl)(setting) } - def partialCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { + def rawCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { val obj = new ObjectValue(tp = cls.typeRef, open = !cls.is(Final) && !cls.isAnonymousClass) // enhancement possible to check if there are actual children // and whether children are possible in other modules. def checkMethod(ddef: tpd.DefDef): Unit = { val sym = ddef.symbol - if (!sym.isEffectivePartial) return + if (!sym.isEffectiveRaw) return val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isPartial) root.add(cls, BlankValue) - else root.add(cls, PartialValue) + if (sym.isRaw) root.add(cls, BlankValue) + else root.add(cls, RawValue) val value = analyzer.methodValue(ddef)(setting.strict) val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) @@ -159,24 +158,24 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkLazy(vdef: tpd.ValDef): Unit = { val sym = vdef.symbol - if (!sym.isEffectivePartial) return + if (!sym.isEffectiveRaw) return val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isPartial) root.add(cls, BlankValue) - else root.add(cls, PartialValue) + if (sym.isRaw) root.add(cls, BlankValue) + else root.add(cls, RawValue) val value = analyzer.lazyValue(vdef)(setting.strict) val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) if (res.hasErrors) { - ctx.warning("Forcing partial lazy value causes errors", sym.pos) + ctx.warning("Forcing raw lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { val value = res.value.widen()(setting.strict) - if (value != FullValue) ctx.warning("Partial lazy value must return a full value", sym.pos) + if (value != FullValue) ctx.warning("Raw lazy value must return a full value", sym.pos) } } @@ -233,7 +232,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.is(Flags.PrivateOrLocal)) return val actual = obj.select(sym, isStaticDispatch = true).value.widen()(setting.strict) - if (actual == PartialValue) sym.annotate(defn.PartialAnnotType) + if (actual == RawValue) sym.annotate(defn.RawAnnotType) else if (actual == FilledValue) sym.annotate(defn.FilledAnnotType) obj.clearDynamicCalls() diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index d26abe4c94de..f949a4ba225c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -216,7 +216,7 @@ class Env(outerId: Int) extends HeapEntry { } else if (sym.isClass && this.containsClass(sym.asClass)) Res() else { - // How do we know the class/method/field does not capture/use a partial/filled outer? + // How do we know the class/method/field does not capture/use a raw/filled outer? // If method/field exist, then the outer class beyond the method/field is full, // i.e. external methods/fields/classes are always safe. FullValue.select(sym) @@ -307,11 +307,11 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - def isPartialOrFilled(value: Value): Boolean = - value == PartialValue || value == FilledValue + def isRawOrFilled(value: Value): Boolean = + value == RawValue || value == FilledValue - if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) PartialValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isPartial || sym.info.isFilled) }) FilledValue + if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) RawValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isRaw || sym.info.isFilled) }) FilledValue else { // check outer val owner = cls.owner diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 634d83098bdd..168e1f0714e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -62,7 +62,7 @@ sealed trait Value { /** Join two values * - * NoValue < Partial < Filled < Full + * NoValue < Raw < Filled < Full */ def join(other: Value): Value = (this, other) match { case (FullValue, v) => v @@ -71,8 +71,8 @@ sealed trait Value { case (_, NoValue) => NoValue case (BlankValue, _) => BlankValue case (_, BlankValue) => BlankValue - case (PartialValue, _) => PartialValue - case (_, PartialValue) => PartialValue + case (RawValue, _) => RawValue + case (_, RawValue) => RawValue case (v1: OpaqueValue, v2: OpaqueValue) => v1.join(v2) case (o1: ObjectValue, o2: ObjectValue) if o1 `eq` o2 => o1 case (f1: FunctionValue, f2: FunctionValue) if f1 `eq` f2 => f1 @@ -105,14 +105,14 @@ sealed trait Value { case sv: SliceValue => setting.heap(sv.id).asSlice.widen case ov: ObjectValue => - if (ov.open) PartialValue + if (ov.open) RawValue else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => if (acc != FullValue) return FilledValue recur(v).join(acc) } case UnionValue(vs) => vs.foldLeft(FullValue: OpaqueValue) { (acc, v) => - if (v == PartialValue || acc == PartialValue) return PartialValue + if (v == RawValue || acc == RawValue) return RawValue else acc.join(recur(v)) } // case NoValue => NoValue @@ -177,8 +177,8 @@ abstract sealed class OpaqueValue extends SingleValue { def <(that: OpaqueValue): Boolean = (this, that) match { case (FullValue, _) => false - case (FilledValue, PartialValue | FilledValue) => false - case (PartialValue, PartialValue) => false + case (FilledValue, RawValue | FilledValue) => false + case (RawValue, RawValue) => false case _ => true } @@ -235,18 +235,18 @@ object BlankValue extends OpaqueValue { val res = Res(value = FullValue) if (sym.is(Flags.Method)) { - if (!sym.isPartial && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@partial` in order to be called", setting.pos) + if (!sym.isRaw && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { - if (!sym.isPartial) - res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", setting.pos) + if (!sym.isRaw) + res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) } else if (sym.isClass) { - if (!sym.isPartial) - res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", setting.pos) + if (!sym.isRaw) + res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) } else { // field select res += Generic(s"The $sym may not be initialized", setting.pos) @@ -255,7 +255,7 @@ object BlankValue extends OpaqueValue { res } - /** assign to partial is always fine? */ + /** assign to raw is always fine? */ def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -264,8 +264,8 @@ object BlankValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isPartial) { - res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", setting.pos) + if (!cls.isRaw) { + res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) res.value = FullValue return res } @@ -275,7 +275,7 @@ object BlankValue extends OpaqueValue { Res() } - def show(implicit setting: ShowSetting): String = "Partial" + def show(implicit setting: ShowSetting): String = "Raw" override def toString = "blank value" } @@ -284,24 +284,24 @@ object BlankValue extends OpaqueValue { * * TODO: rename to `raw` */ -object PartialValue extends OpaqueValue { +object RawValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) if (sym.is(Flags.Method)) { - if (!sym.isPartial && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@partial` in order to be called", setting.pos) + if (!sym.isRaw && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { - if (!sym.isPartial) - res += Generic(s"The lazy field $sym should be marked as `@partial` in order to be accessed", setting.pos) + if (!sym.isRaw) + res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) } else if (sym.isClass) { - if (!sym.isPartial) - res += Generic(s"The nested $sym should be marked as `@partial` in order to be instantiated", setting.pos) + if (!sym.isRaw) + res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) } else { // field select if (!sym.isClassParam) @@ -311,7 +311,7 @@ object PartialValue extends OpaqueValue { res } - /** assign to partial is always fine? */ + /** assign to raw is always fine? */ def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -320,8 +320,8 @@ object PartialValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isPartial) { - res += Generic(s"The nested $cls should be marked as `@partial` in order to be instantiated", setting.pos) + if (!cls.isRaw) { + res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) res.value = FullValue return res } @@ -331,22 +331,22 @@ object PartialValue extends OpaqueValue { Res() } - def show(implicit setting: ShowSetting): String = "Partial" + def show(implicit setting: ShowSetting): String = "Raw" - override def toString = "partial value" + override def toString = "raw value" } object FilledValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { - if (!sym.isPartial && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) + if (!sym.isRaw && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) res += Generic(s"The $sym should be marked as `@init` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy) && !sym.isEffectiveInit) { - if (!sym.isPartial && !sym.isFilled) + if (!sym.isRaw && !sym.isFilled) res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) res.value = sym.info.value.join(sym.value) @@ -369,7 +369,7 @@ object FilledValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isPartial && !cls.isFilled) { + if (!cls.isRaw && !cls.isFilled) { res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) res.value = FullValue return res @@ -602,7 +602,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { // select on self type if (!target.exists) { if (sym.owner.is(Flags.Trait)) - return PartialValue.select(sym) + return RawValue.select(sym) else return FilledValue.select(sym) } @@ -634,7 +634,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val target = resolve(sym) // select on self type - if (!target.exists) return PartialValue.assign(sym, value) + if (!target.exists) return RawValue.assign(sym, value) val cls = target.owner.asClass if (slices.contains(cls)) { @@ -654,7 +654,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { slices(outerCls).init(constr, values, argPos, obj) } else { - val value = if (cls.isDefinedOn(tp)) FilledValue else PartialValue + val value = if (cls.isDefinedOn(tp)) FilledValue else RawValue value.init(constr, values, argPos, obj) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 94f40f6fc8e3..cbfbbccfe934 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -23,12 +23,12 @@ import collection.mutable package object init { implicit class TypeOps(val tp: Type) extends AnyVal { - def isPartial(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.PartialAnnot) + def isRaw(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.RawAnnot) def isFilled(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.FilledAnnot) def value(implicit ctx: Context) = - if (isPartial) PartialValue + if (isRaw) RawValue else if (isFilled) FilledValue else FullValue } @@ -39,17 +39,17 @@ package object init { } implicit class SymOps(val sym: Symbol) extends AnyVal { - def isPartial(implicit ctx: Context) = sym.hasAnnotation(defn.PartialAnnot) + def isRaw(implicit ctx: Context) = sym.hasAnnotation(defn.RawAnnot) def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) - def isEffectivePartial(implicit ctx: Context) = - sym.isPartial || sym.isCalledAbove(sym.owner.asClass) + def isEffectiveRaw(implicit ctx: Context) = + sym.isRaw || sym.isCalledAbove(sym.owner.asClass) def isEffectiveInit(implicit ctx: Context) = - !sym.isEffectivePartial && + !sym.isEffectiveRaw && (sym.isInit || sym.allOverriddenSymbols.exists(_.isInit)) def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = @@ -64,7 +64,7 @@ package object init { tp.classSymbol.isSubClass(sym.owner) def value(implicit ctx: Context) = - if (isPartial) PartialValue + if (isRaw) RawValue else if (isFilled) FilledValue else FullValue diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 6c4dee3ca1fd..6c9422f7f68e 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -41,6 +41,6 @@ object DottyPredef { @forceInline def locally[T](body: => T): T = body - type Partial[T] = T @scala.annotation.partial + type Raw[T] = T @scala.annotation.raw type Filled[T] = T @scala.annotation.filled } diff --git a/library/src/scala/annotation/partial.scala b/library/src/scala/annotation/raw.scala similarity index 77% rename from library/src/scala/annotation/partial.scala rename to library/src/scala/annotation/raw.scala index 8025907440c2..823942e194f2 100644 --- a/library/src/scala/annotation/partial.scala +++ b/library/src/scala/annotation/raw.scala @@ -5,9 +5,9 @@ package scala.annotation * be assigned yet. * * When used on methods, it means `this` and `super` - * are partial. + * are raw. * * When used on constructors, it means the immediate - * outer is partial. + * outer is raw. */ -class partial extends StaticAnnotation +class raw extends StaticAnnotation diff --git a/tests/neg-custom-args/safe-init/cycle.scala b/tests/neg-custom-args/safe-init/cycle.scala index 8c84df26afb2..39b3651f99af 100644 --- a/tests/neg-custom-args/safe-init/cycle.scala +++ b/tests/neg-custom-args/safe-init/cycle.scala @@ -7,7 +7,7 @@ class Parent { def show = child.show } -class Child(parent: Partial[Parent]) { +class Child(parent: Raw[Parent]) { val name = "child" println(parent.name) // error diff --git a/tests/neg-custom-args/safe-init/double-list2.scala b/tests/neg-custom-args/safe-init/double-list2.scala index 77c63c406674..536c71c7e43f 100644 --- a/tests/neg-custom-args/safe-init/double-list2.scala +++ b/tests/neg-custom-args/safe-init/double-list2.scala @@ -4,7 +4,7 @@ class List { def insert(data: AnyRef) = sentinel.insertAfter(data) } -class Node(var prev: Node, var next: Node, parent: Partial[List], data: AnyRef) { +class Node(var prev: Node, var next: Node, parent: Raw[List], data: AnyRef) { parent.insert("hello") // error def insertAfter(data: AnyRef) = { diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala index c2e5795b9c2a..3b570c837665 100644 --- a/tests/neg-custom-args/safe-init/example.scala +++ b/tests/neg-custom-args/safe-init/example.scala @@ -6,12 +6,12 @@ class Parent(x: Int) { lazy val l1 = name.size // ok: l1 is lazy lazy val l2 = addr.size // error: l2 is forced at L32 before `addr` is initialized - val fun: Int => Int = n => n + list.size // ok, fun is a partial value - val bar: Bar = new Bar("bar", fun) // ok, Bar accepts partial value + val fun: Int => Int = n => n + list.size // ok, fun is a raw value + val bar: Bar = new Bar("bar", fun) // ok, Bar accepts raw value bar.result // error: bar depends on list - val child = new Child(this) // error: `this` is partial, while full value expected - List(5, 9).map(n => n + list.size) // error: partial value used as full value + val child = new Child(this) // error: `this` is raw, while full value expected + List(5, 9).map(n => n + list.size) // error: raw value used as full value f(20) // error: list not initialized @@ -35,7 +35,7 @@ class Parent(x: Int) { } -class Bar(val name: String, fun: Partial[Int => Int]) { +class Bar(val name: String, fun: Raw[Int => Int]) { def result = fun(20) } diff --git a/tests/neg-custom-args/safe-init/function.scala b/tests/neg-custom-args/safe-init/function.scala index 3799ab7ad9f7..08e5f7f6907c 100644 --- a/tests/neg-custom-args/safe-init/function.scala +++ b/tests/neg-custom-args/safe-init/function.scala @@ -4,7 +4,7 @@ class Foo { val fun2: Int => Int = n => 1 + n + list.size // error: fun is called in the next line fun2(5) // error: latent effects - List(5, 9).map(n => 2 + n + list.size) // error: closure is partial, but a full value expected + List(5, 9).map(n => 2 + n + list.size) // error: closure is raw, but a full value expected val list = List(1, 2, 3) diff --git a/tests/neg-custom-args/safe-init/function2.scala b/tests/neg-custom-args/safe-init/function2.scala index ef50c123d7f9..6a467b8b7418 100644 --- a/tests/neg-custom-args/safe-init/function2.scala +++ b/tests/neg-custom-args/safe-init/function2.scala @@ -1,6 +1,6 @@ class Foo { def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below - fun(5) // error: select on partial value + fun(5) // error: select on raw value def getThis: Foo = this getThis.x // error diff --git a/tests/neg-custom-args/safe-init/if.scala b/tests/neg-custom-args/safe-init/if.scala index 2463d9a1d3a2..bc61abc59b6a 100644 --- a/tests/neg-custom-args/safe-init/if.scala +++ b/tests/neg-custom-args/safe-init/if.scala @@ -25,7 +25,7 @@ class Foo(x: Int) { } -class Bar(x: Int, m: Partial[String]) { +class Bar(x: Int, m: Raw[String]) { var from: String = _ var to: String = _ val message = "hello, world" diff --git a/tests/neg-custom-args/safe-init/inner1.scala b/tests/neg-custom-args/safe-init/inner1.scala index 7fb35736a6f9..8b4937fb67f1 100644 --- a/tests/neg-custom-args/safe-init/inner1.scala +++ b/tests/neg-custom-args/safe-init/inner1.scala @@ -20,7 +20,7 @@ object lib { def escape(x: Foo#Inner): Unit = ??? } -class Bar(val foo: Partial[Foo]) { +class Bar(val foo: Raw[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner2.scala b/tests/neg-custom-args/safe-init/inner2.scala index fab717df64ea..e3895ae1252f 100644 --- a/tests/neg-custom-args/safe-init/inner2.scala +++ b/tests/neg-custom-args/safe-init/inner2.scala @@ -9,7 +9,7 @@ class Foo { new bar.Inner // error } -class Bar(val foo: Partial[Foo]) { +class Bar(val foo: Raw[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner3.scala b/tests/neg-custom-args/safe-init/inner3.scala index d7925979346a..fe4d5cce41ae 100644 --- a/tests/neg-custom-args/safe-init/inner3.scala +++ b/tests/neg-custom-args/safe-init/inner3.scala @@ -1,6 +1,6 @@ class Foo { val bar = new Bar(this) - var x: bar.Inner = _ // ok, partial value as type prefix + var x: bar.Inner = _ // ok, raw value as type prefix class Inner { val len = list.size @@ -11,7 +11,7 @@ class Foo { x = null } -class Bar(val foo: Partial[Foo]) { +class Bar(val foo: Raw[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner4.scala b/tests/neg-custom-args/safe-init/inner4.scala index 3216644b6aed..0754a662c916 100644 --- a/tests/neg-custom-args/safe-init/inner4.scala +++ b/tests/neg-custom-args/safe-init/inner4.scala @@ -1,4 +1,4 @@ -class Foo(val foo1: Partial[Foo], val foo2: Foo) { +class Foo(val foo1: Raw[Foo], val foo2: Foo) { class Inner { val len = name.size // error } diff --git a/tests/neg-custom-args/safe-init/override11.scala b/tests/neg-custom-args/safe-init/override11.scala index 0c6c511d7c02..3bed2010fe01 100644 --- a/tests/neg-custom-args/safe-init/override11.scala +++ b/tests/neg-custom-args/safe-init/override11.scala @@ -1,4 +1,4 @@ -class B(a: Partial[A]) { +class B(a: Raw[A]) { val m: A = a } diff --git a/tests/neg-custom-args/safe-init/override4.scala b/tests/neg-custom-args/safe-init/override4.scala index b82d7c9f6b63..502bf0ac78ae 100644 --- a/tests/neg-custom-args/safe-init/override4.scala +++ b/tests/neg-custom-args/safe-init/override4.scala @@ -1,4 +1,4 @@ -import scala.annotation.partial +import scala.annotation.raw import scala.annotation.init import scala.collection.mutable @@ -25,7 +25,7 @@ class Bar2 extends Bar { class Foo1 { val map: mutable.Map[Int, String] = mutable.Map.empty - @partial + @raw def enter(k: Int, v: String) = map(k) = v // error // error } @@ -33,7 +33,7 @@ class Foo1 { abstract class Foo2 { def map: mutable.Map[Int, String] - @partial + @raw def enter(k: Int, v: String) = map(k) = v // error // error } diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala index 4ee0eb2dbad0..9ab3bfb81958 100644 --- a/tests/neg-custom-args/safe-init/override7.scala +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -29,6 +29,6 @@ trait Dao(val name: String) extends Foo { trait Zen(val name: String) { val title = "Mr." - @scala.annotation.partial + @scala.annotation.raw def getName = name // error: cannot access `name` // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override8.scala b/tests/neg-custom-args/safe-init/override8.scala index c97a049bec3e..059be26a9daa 100644 --- a/tests/neg-custom-args/safe-init/override8.scala +++ b/tests/neg-custom-args/safe-init/override8.scala @@ -1,4 +1,4 @@ -import scala.annotation.partial +import scala.annotation.raw trait Foo { val x = "world" @@ -15,12 +15,12 @@ trait Bar { def foo(x: String) = "hello, " + x } -class Qux extends Foo with Bar // error: Bar.foo needs to be annotated with `@partial` +class Qux extends Foo with Bar // error: Bar.foo needs to be annotated with `@raw` trait Yun { val m = "hello" - @partial + @raw def foo(n: Int) = m // error // error } diff --git a/tests/neg-custom-args/safe-init/parent3.scala b/tests/neg-custom-args/safe-init/parent3.scala index 513e73e8e740..3013754d81be 100644 --- a/tests/neg-custom-args/safe-init/parent3.scala +++ b/tests/neg-custom-args/safe-init/parent3.scala @@ -1,11 +1,11 @@ -abstract class Parent(p: Partial[String]) { +abstract class Parent(p: Raw[String]) { val x = "name" lazy val z = bar def foo = bar def bar: Int } -class Child(o: Partial[String]) extends Parent(o) { +class Child(o: Raw[String]) extends Parent(o) { val y = "hello" val m = this.x diff --git a/tests/neg-custom-args/safe-init/parent7.scala b/tests/neg-custom-args/safe-init/parent7.scala index 5c25b9ab1ae7..d7e3ff205d1a 100644 --- a/tests/neg-custom-args/safe-init/parent7.scala +++ b/tests/neg-custom-args/safe-init/parent7.scala @@ -1,12 +1,12 @@ trait Foo { - @scala.annotation.partial + @scala.annotation.raw def name: String def title: String } trait Bar { this: Foo => - val message = "hello, " + name // ok: because `name` is marked partial + val message = "hello, " + name // ok: because `name` is marked raw println(title) // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/partial-select1.scala b/tests/neg-custom-args/safe-init/partial-select1.scala index dbf7c62d659a..a39129fde86a 100644 --- a/tests/neg-custom-args/safe-init/partial-select1.scala +++ b/tests/neg-custom-args/safe-init/partial-select1.scala @@ -5,7 +5,7 @@ class Parent { val name = "parent" } -class Child(parent: Partial[Parent]) { +class Child(parent: Raw[Parent]) { val name = "child" var number = 0 diff --git a/tests/neg-custom-args/safe-init/simple-1f.scala b/tests/neg-custom-args/safe-init/simple-1f.scala index 1d0f29ad354d..2f10c48f9c7f 100644 --- a/tests/neg-custom-args/safe-init/simple-1f.scala +++ b/tests/neg-custom-args/safe-init/simple-1f.scala @@ -1,4 +1,4 @@ -class Foo(x: Partial[String]) { +class Foo(x: Raw[String]) { var name: String = _ name.size // error @@ -11,7 +11,7 @@ class Foo(x: Partial[String]) { name = x } -class Bar(x: Partial[String]) { +class Bar(x: Raw[String]) { var name: String = x name.size // error diff --git a/tests/neg-custom-args/safe-init/simple-1h.scala b/tests/neg-custom-args/safe-init/simple-1h.scala index a2a1a1ab753b..9be10b4229f7 100644 --- a/tests/neg-custom-args/safe-init/simple-1h.scala +++ b/tests/neg-custom-args/safe-init/simple-1h.scala @@ -1,4 +1,4 @@ -class Foo(n: Partial[String]) { +class Foo(n: Raw[String]) { foo(new Foo("Jack")) // recursive creation val name: String = n diff --git a/tests/neg-custom-args/safe-init/simple-1i.scala b/tests/neg-custom-args/safe-init/simple-1i.scala index 39785ca45510..6b523ebeb2ae 100644 --- a/tests/neg-custom-args/safe-init/simple-1i.scala +++ b/tests/neg-custom-args/safe-init/simple-1i.scala @@ -1,4 +1,4 @@ -class Foo(n: Partial[String]) { +class Foo(n: Raw[String]) { foo(new Foo("Jack")) // recursive creation val name: String = n From 9423e21500a6b6e4809b7ef3d0697349c2cec354 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 10:58:47 +0200 Subject: [PATCH 16/83] Rename raw to cold --- .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../tools/dotc/transform/init/Checker.scala | 30 ++++++------ .../tools/dotc/transform/init/Heap.scala | 8 +-- .../tools/dotc/transform/init/Values.scala | 49 +++++++++---------- .../tools/dotc/transform/init/package.scala | 14 +++--- library/src/dotty/DottyPredef.scala | 2 +- .../annotation/{raw.scala => cold.scala} | 6 +-- tests/neg-custom-args/safe-init/cycle.scala | 2 +- .../safe-init/double-list2.scala | 2 +- tests/neg-custom-args/safe-init/example.scala | 10 ++-- .../neg-custom-args/safe-init/function.scala | 2 +- .../neg-custom-args/safe-init/function2.scala | 2 +- tests/neg-custom-args/safe-init/if.scala | 2 +- tests/neg-custom-args/safe-init/inner1.scala | 2 +- tests/neg-custom-args/safe-init/inner2.scala | 2 +- tests/neg-custom-args/safe-init/inner3.scala | 4 +- tests/neg-custom-args/safe-init/inner4.scala | 2 +- .../safe-init/override11.scala | 2 +- .../neg-custom-args/safe-init/override4.scala | 6 +-- .../neg-custom-args/safe-init/override7.scala | 2 +- .../neg-custom-args/safe-init/override8.scala | 6 +-- tests/neg-custom-args/safe-init/parent3.scala | 4 +- tests/neg-custom-args/safe-init/parent7.scala | 4 +- .../safe-init/partial-select1.scala | 2 +- .../neg-custom-args/safe-init/simple-1f.scala | 4 +- .../neg-custom-args/safe-init/simple-1h.scala | 2 +- .../neg-custom-args/safe-init/simple-1i.scala | 2 +- 27 files changed, 88 insertions(+), 89 deletions(-) rename library/src/scala/annotation/{raw.scala => cold.scala} (79%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5437590e2e28..2e3bc5c6f6c5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -806,8 +806,8 @@ class Definitions { def SetterMetaAnnot(implicit ctx: Context): ClassSymbol = SetterMetaAnnotType.symbol.asClass lazy val ShowAsInfixAnotType: TypeRef = ctx.requiredClassRef("scala.annotation.showAsInfix") def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass - lazy val RawAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.raw") - def RawAnnot(implicit ctx: Context): ClassSymbol = RawAnnotType.symbol.asClass + lazy val ColdAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.cold") + def ColdAnnot(implicit ctx: Context): ClassSymbol = ColdAnnotType.symbol.asClass lazy val FilledAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.filled") def FilledAnnot(implicit ctx: Context): ClassSymbol = FilledAnnotType.symbol.asClass lazy val InitAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.init") diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 22c979f146ab..494cd5ef8ef4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -29,13 +29,13 @@ object Checker { /** This transform checks initialization is safe based on data-flow analysis * - * - Raw - * - Filled - * - Full + * - Cold + * - Warm + * - Hot * - * 1. A _full_ object is fully initialized. - * 2. All fields of a _filled_ object are assigned, but the fields may refer to non-full objects. - * 3. A _raw_ object may have unassigned fields. + * 1. A _hot_ object is fully initialized. + * 2. All fields of a _warm_ object are assigned, but the fields may refer to non-full objects. + * 3. A _cold_ object may have unassigned fields. * * TODO: * - check default arguments of init methods @@ -79,7 +79,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => mbrd <- self.member(mbr.name).alternatives tp = mbr.info.asSeenFrom(self, mbr.owner) if mbrd.info.overrides(tp, matchLoosely = true) && - !mbrd.symbol.isInit && !mbrd.symbol.isRaw & + !mbrd.symbol.isInit && !mbrd.symbol.isCold & !mbrd.symbol.isCalledAbove(cls.asClass) && !mbrd.symbol.is(Deferred) } ctx.warning(invalidImplementMsg(mbrd.symbol), cls.pos) @@ -136,13 +136,13 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkMethod(ddef: tpd.DefDef): Unit = { val sym = ddef.symbol - if (!sym.isEffectiveRaw) return + if (!sym.isEffectiveCold) return val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isRaw) root.add(cls, BlankValue) - else root.add(cls, RawValue) + if (sym.isCold) root.add(cls, BlankValue) + else root.add(cls, ColdValue) val value = analyzer.methodValue(ddef)(setting.strict) val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) @@ -158,13 +158,13 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkLazy(vdef: tpd.ValDef): Unit = { val sym = vdef.symbol - if (!sym.isEffectiveRaw) return + if (!sym.isEffectiveCold) return val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isRaw) root.add(cls, BlankValue) - else root.add(cls, RawValue) + if (sym.isCold) root.add(cls, BlankValue) + else root.add(cls, ColdValue) val value = analyzer.lazyValue(vdef)(setting.strict) val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) @@ -175,7 +175,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } else { val value = res.value.widen()(setting.strict) - if (value != FullValue) ctx.warning("Raw lazy value must return a full value", sym.pos) + if (value != FullValue) ctx.warning("Cold lazy value must return a full value", sym.pos) } } @@ -232,7 +232,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.is(Flags.PrivateOrLocal)) return val actual = obj.select(sym, isStaticDispatch = true).value.widen()(setting.strict) - if (actual == RawValue) sym.annotate(defn.RawAnnotType) + if (actual == ColdValue) sym.annotate(defn.ColdAnnotType) else if (actual == FilledValue) sym.annotate(defn.FilledAnnotType) obj.clearDynamicCalls() diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index f949a4ba225c..e5f7bdb5eb65 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,11 +307,11 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - def isRawOrFilled(value: Value): Boolean = - value == RawValue || value == FilledValue + def isColdOrFilled(value: Value): Boolean = + value == ColdValue || value == FilledValue - if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) RawValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isRaw || sym.info.isFilled) }) FilledValue + if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) ColdValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isCold || sym.info.isFilled) }) FilledValue else { // check outer val owner = cls.owner diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 168e1f0714e0..11e1c345c5f6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -62,7 +62,7 @@ sealed trait Value { /** Join two values * - * NoValue < Raw < Filled < Full + * NoValue < Cold < Filled < Full */ def join(other: Value): Value = (this, other) match { case (FullValue, v) => v @@ -71,8 +71,8 @@ sealed trait Value { case (_, NoValue) => NoValue case (BlankValue, _) => BlankValue case (_, BlankValue) => BlankValue - case (RawValue, _) => RawValue - case (_, RawValue) => RawValue + case (ColdValue, _) => ColdValue + case (_, ColdValue) => ColdValue case (v1: OpaqueValue, v2: OpaqueValue) => v1.join(v2) case (o1: ObjectValue, o2: ObjectValue) if o1 `eq` o2 => o1 case (f1: FunctionValue, f2: FunctionValue) if f1 `eq` f2 => f1 @@ -105,14 +105,14 @@ sealed trait Value { case sv: SliceValue => setting.heap(sv.id).asSlice.widen case ov: ObjectValue => - if (ov.open) RawValue + if (ov.open) ColdValue else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => if (acc != FullValue) return FilledValue recur(v).join(acc) } case UnionValue(vs) => vs.foldLeft(FullValue: OpaqueValue) { (acc, v) => - if (v == RawValue || acc == RawValue) return RawValue + if (v == ColdValue || acc == ColdValue) return ColdValue else acc.join(recur(v)) } // case NoValue => NoValue @@ -177,8 +177,8 @@ abstract sealed class OpaqueValue extends SingleValue { def <(that: OpaqueValue): Boolean = (this, that) match { case (FullValue, _) => false - case (FilledValue, RawValue | FilledValue) => false - case (RawValue, RawValue) => false + case (FilledValue, ColdValue | FilledValue) => false + case (ColdValue, ColdValue) => false case _ => true } @@ -235,17 +235,17 @@ object BlankValue extends OpaqueValue { val res = Res(value = FullValue) if (sym.is(Flags.Method)) { - if (!sym.isRaw && !sym.name.is(DefaultGetterName)) + if (!sym.isCold && !sym.name.is(DefaultGetterName)) res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { - if (!sym.isRaw) + if (!sym.isCold) res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) } else if (sym.isClass) { - if (!sym.isRaw) + if (!sym.isCold) res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) } else { // field select @@ -264,7 +264,7 @@ object BlankValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isRaw) { + if (!cls.isCold) { res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) res.value = FullValue return res @@ -275,32 +275,31 @@ object BlankValue extends OpaqueValue { Res() } - def show(implicit setting: ShowSetting): String = "Raw" + def show(implicit setting: ShowSetting): String = "Cold" override def toString = "blank value" } /** A raw value, where class/trait params are initialized, but body fields are not * - * TODO: rename to `raw` */ -object RawValue extends OpaqueValue { +object ColdValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { // set state to Full, don't report same error message again val res = Res(value = FullValue) if (sym.is(Flags.Method)) { - if (!sym.isRaw && !sym.name.is(DefaultGetterName)) + if (!sym.isCold && !sym.name.is(DefaultGetterName)) res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { - if (!sym.isRaw) + if (!sym.isCold) res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) } else if (sym.isClass) { - if (!sym.isRaw) + if (!sym.isCold) res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) } else { // field select @@ -320,7 +319,7 @@ object RawValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isRaw) { + if (!cls.isCold) { res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) res.value = FullValue return res @@ -331,7 +330,7 @@ object RawValue extends OpaqueValue { Res() } - def show(implicit setting: ShowSetting): String = "Raw" + def show(implicit setting: ShowSetting): String = "Cold" override def toString = "raw value" } @@ -340,13 +339,13 @@ object FilledValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { - if (!sym.isRaw && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) + if (!sym.isCold && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) res += Generic(s"The $sym should be marked as `@init` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy) && !sym.isEffectiveInit) { - if (!sym.isRaw && !sym.isFilled) + if (!sym.isCold && !sym.isFilled) res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) res.value = sym.info.value.join(sym.value) @@ -369,7 +368,7 @@ object FilledValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isRaw && !cls.isFilled) { + if (!cls.isCold && !cls.isFilled) { res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) res.value = FullValue return res @@ -602,7 +601,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { // select on self type if (!target.exists) { if (sym.owner.is(Flags.Trait)) - return RawValue.select(sym) + return ColdValue.select(sym) else return FilledValue.select(sym) } @@ -634,7 +633,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val target = resolve(sym) // select on self type - if (!target.exists) return RawValue.assign(sym, value) + if (!target.exists) return ColdValue.assign(sym, value) val cls = target.owner.asClass if (slices.contains(cls)) { @@ -654,7 +653,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { slices(outerCls).init(constr, values, argPos, obj) } else { - val value = if (cls.isDefinedOn(tp)) FilledValue else RawValue + val value = if (cls.isDefinedOn(tp)) FilledValue else ColdValue value.init(constr, values, argPos, obj) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index cbfbbccfe934..2aa786015caf 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -23,12 +23,12 @@ import collection.mutable package object init { implicit class TypeOps(val tp: Type) extends AnyVal { - def isRaw(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.RawAnnot) + def isCold(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.ColdAnnot) def isFilled(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.FilledAnnot) def value(implicit ctx: Context) = - if (isRaw) RawValue + if (isCold) ColdValue else if (isFilled) FilledValue else FullValue } @@ -39,17 +39,17 @@ package object init { } implicit class SymOps(val sym: Symbol) extends AnyVal { - def isRaw(implicit ctx: Context) = sym.hasAnnotation(defn.RawAnnot) + def isCold(implicit ctx: Context) = sym.hasAnnotation(defn.ColdAnnot) def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) - def isEffectiveRaw(implicit ctx: Context) = - sym.isRaw || sym.isCalledAbove(sym.owner.asClass) + def isEffectiveCold(implicit ctx: Context) = + sym.isCold || sym.isCalledAbove(sym.owner.asClass) def isEffectiveInit(implicit ctx: Context) = - !sym.isEffectiveRaw && + !sym.isEffectiveCold && (sym.isInit || sym.allOverriddenSymbols.exists(_.isInit)) def isCalledIn(cls: ClassSymbol)(implicit ctx: Context): Boolean = @@ -64,7 +64,7 @@ package object init { tp.classSymbol.isSubClass(sym.owner) def value(implicit ctx: Context) = - if (isRaw) RawValue + if (isCold) ColdValue else if (isFilled) FilledValue else FullValue diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 6c9422f7f68e..b9f424be1b1f 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -41,6 +41,6 @@ object DottyPredef { @forceInline def locally[T](body: => T): T = body - type Raw[T] = T @scala.annotation.raw + type Cold[T] = T @scala.annotation.cold type Filled[T] = T @scala.annotation.filled } diff --git a/library/src/scala/annotation/raw.scala b/library/src/scala/annotation/cold.scala similarity index 79% rename from library/src/scala/annotation/raw.scala rename to library/src/scala/annotation/cold.scala index 823942e194f2..97a2d1214a2b 100644 --- a/library/src/scala/annotation/raw.scala +++ b/library/src/scala/annotation/cold.scala @@ -5,9 +5,9 @@ package scala.annotation * be assigned yet. * * When used on methods, it means `this` and `super` - * are raw. + * are cold. * * When used on constructors, it means the immediate - * outer is raw. + * outer is cold. */ -class raw extends StaticAnnotation +class cold extends StaticAnnotation diff --git a/tests/neg-custom-args/safe-init/cycle.scala b/tests/neg-custom-args/safe-init/cycle.scala index 39b3651f99af..b9e718b67bcb 100644 --- a/tests/neg-custom-args/safe-init/cycle.scala +++ b/tests/neg-custom-args/safe-init/cycle.scala @@ -7,7 +7,7 @@ class Parent { def show = child.show } -class Child(parent: Raw[Parent]) { +class Child(parent: Cold[Parent]) { val name = "child" println(parent.name) // error diff --git a/tests/neg-custom-args/safe-init/double-list2.scala b/tests/neg-custom-args/safe-init/double-list2.scala index 536c71c7e43f..7f17de7184d2 100644 --- a/tests/neg-custom-args/safe-init/double-list2.scala +++ b/tests/neg-custom-args/safe-init/double-list2.scala @@ -4,7 +4,7 @@ class List { def insert(data: AnyRef) = sentinel.insertAfter(data) } -class Node(var prev: Node, var next: Node, parent: Raw[List], data: AnyRef) { +class Node(var prev: Node, var next: Node, parent: Cold[List], data: AnyRef) { parent.insert("hello") // error def insertAfter(data: AnyRef) = { diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala index 3b570c837665..e48c4ca1c09e 100644 --- a/tests/neg-custom-args/safe-init/example.scala +++ b/tests/neg-custom-args/safe-init/example.scala @@ -6,12 +6,12 @@ class Parent(x: Int) { lazy val l1 = name.size // ok: l1 is lazy lazy val l2 = addr.size // error: l2 is forced at L32 before `addr` is initialized - val fun: Int => Int = n => n + list.size // ok, fun is a raw value - val bar: Bar = new Bar("bar", fun) // ok, Bar accepts raw value + val fun: Int => Int = n => n + list.size // ok, fun is a cold value + val bar: Bar = new Bar("bar", fun) // ok, Bar accepts cold value bar.result // error: bar depends on list - val child = new Child(this) // error: `this` is raw, while full value expected - List(5, 9).map(n => n + list.size) // error: raw value used as full value + val child = new Child(this) // error: `this` is cold, while full value expected + List(5, 9).map(n => n + list.size) // error: cold value used as full value f(20) // error: list not initialized @@ -35,7 +35,7 @@ class Parent(x: Int) { } -class Bar(val name: String, fun: Raw[Int => Int]) { +class Bar(val name: String, fun: Cold[Int => Int]) { def result = fun(20) } diff --git a/tests/neg-custom-args/safe-init/function.scala b/tests/neg-custom-args/safe-init/function.scala index 08e5f7f6907c..9dfbaf226b04 100644 --- a/tests/neg-custom-args/safe-init/function.scala +++ b/tests/neg-custom-args/safe-init/function.scala @@ -4,7 +4,7 @@ class Foo { val fun2: Int => Int = n => 1 + n + list.size // error: fun is called in the next line fun2(5) // error: latent effects - List(5, 9).map(n => 2 + n + list.size) // error: closure is raw, but a full value expected + List(5, 9).map(n => 2 + n + list.size) // error: closure is cold, but a full value expected val list = List(1, 2, 3) diff --git a/tests/neg-custom-args/safe-init/function2.scala b/tests/neg-custom-args/safe-init/function2.scala index 6a467b8b7418..029b85e89bc2 100644 --- a/tests/neg-custom-args/safe-init/function2.scala +++ b/tests/neg-custom-args/safe-init/function2.scala @@ -1,6 +1,6 @@ class Foo { def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below - fun(5) // error: select on raw value + fun(5) // error: select on cold value def getThis: Foo = this getThis.x // error diff --git a/tests/neg-custom-args/safe-init/if.scala b/tests/neg-custom-args/safe-init/if.scala index bc61abc59b6a..f6ad82e6881e 100644 --- a/tests/neg-custom-args/safe-init/if.scala +++ b/tests/neg-custom-args/safe-init/if.scala @@ -25,7 +25,7 @@ class Foo(x: Int) { } -class Bar(x: Int, m: Raw[String]) { +class Bar(x: Int, m: Cold[String]) { var from: String = _ var to: String = _ val message = "hello, world" diff --git a/tests/neg-custom-args/safe-init/inner1.scala b/tests/neg-custom-args/safe-init/inner1.scala index 8b4937fb67f1..ff9470c6830f 100644 --- a/tests/neg-custom-args/safe-init/inner1.scala +++ b/tests/neg-custom-args/safe-init/inner1.scala @@ -20,7 +20,7 @@ object lib { def escape(x: Foo#Inner): Unit = ??? } -class Bar(val foo: Raw[Foo]) { +class Bar(val foo: Cold[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner2.scala b/tests/neg-custom-args/safe-init/inner2.scala index e3895ae1252f..f96218edac33 100644 --- a/tests/neg-custom-args/safe-init/inner2.scala +++ b/tests/neg-custom-args/safe-init/inner2.scala @@ -9,7 +9,7 @@ class Foo { new bar.Inner // error } -class Bar(val foo: Raw[Foo]) { +class Bar(val foo: Cold[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner3.scala b/tests/neg-custom-args/safe-init/inner3.scala index fe4d5cce41ae..db1c6e640350 100644 --- a/tests/neg-custom-args/safe-init/inner3.scala +++ b/tests/neg-custom-args/safe-init/inner3.scala @@ -1,6 +1,6 @@ class Foo { val bar = new Bar(this) - var x: bar.Inner = _ // ok, raw value as type prefix + var x: bar.Inner = _ // ok, cold value as type prefix class Inner { val len = list.size @@ -11,7 +11,7 @@ class Foo { x = null } -class Bar(val foo: Raw[Foo]) { +class Bar(val foo: Cold[Foo]) { val inner = new foo.Inner // error class Inner { diff --git a/tests/neg-custom-args/safe-init/inner4.scala b/tests/neg-custom-args/safe-init/inner4.scala index 0754a662c916..9877faca2adc 100644 --- a/tests/neg-custom-args/safe-init/inner4.scala +++ b/tests/neg-custom-args/safe-init/inner4.scala @@ -1,4 +1,4 @@ -class Foo(val foo1: Raw[Foo], val foo2: Foo) { +class Foo(val foo1: Cold[Foo], val foo2: Foo) { class Inner { val len = name.size // error } diff --git a/tests/neg-custom-args/safe-init/override11.scala b/tests/neg-custom-args/safe-init/override11.scala index 3bed2010fe01..79cf80a920e8 100644 --- a/tests/neg-custom-args/safe-init/override11.scala +++ b/tests/neg-custom-args/safe-init/override11.scala @@ -1,4 +1,4 @@ -class B(a: Raw[A]) { +class B(a: Cold[A]) { val m: A = a } diff --git a/tests/neg-custom-args/safe-init/override4.scala b/tests/neg-custom-args/safe-init/override4.scala index 502bf0ac78ae..e355ed231ded 100644 --- a/tests/neg-custom-args/safe-init/override4.scala +++ b/tests/neg-custom-args/safe-init/override4.scala @@ -1,4 +1,4 @@ -import scala.annotation.raw +import scala.annotation.cold import scala.annotation.init import scala.collection.mutable @@ -25,7 +25,7 @@ class Bar2 extends Bar { class Foo1 { val map: mutable.Map[Int, String] = mutable.Map.empty - @raw + @cold def enter(k: Int, v: String) = map(k) = v // error // error } @@ -33,7 +33,7 @@ class Foo1 { abstract class Foo2 { def map: mutable.Map[Int, String] - @raw + @cold def enter(k: Int, v: String) = map(k) = v // error // error } diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala index 9ab3bfb81958..3d1504cf6170 100644 --- a/tests/neg-custom-args/safe-init/override7.scala +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -29,6 +29,6 @@ trait Dao(val name: String) extends Foo { trait Zen(val name: String) { val title = "Mr." - @scala.annotation.raw + @scala.annotation.cold def getName = name // error: cannot access `name` // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override8.scala b/tests/neg-custom-args/safe-init/override8.scala index 059be26a9daa..37383de5f948 100644 --- a/tests/neg-custom-args/safe-init/override8.scala +++ b/tests/neg-custom-args/safe-init/override8.scala @@ -1,4 +1,4 @@ -import scala.annotation.raw +import scala.annotation.cold trait Foo { val x = "world" @@ -15,12 +15,12 @@ trait Bar { def foo(x: String) = "hello, " + x } -class Qux extends Foo with Bar // error: Bar.foo needs to be annotated with `@raw` +class Qux extends Foo with Bar // error: Bar.foo needs to be annotated with `@cold` trait Yun { val m = "hello" - @raw + @cold def foo(n: Int) = m // error // error } diff --git a/tests/neg-custom-args/safe-init/parent3.scala b/tests/neg-custom-args/safe-init/parent3.scala index 3013754d81be..0475d2f2160c 100644 --- a/tests/neg-custom-args/safe-init/parent3.scala +++ b/tests/neg-custom-args/safe-init/parent3.scala @@ -1,11 +1,11 @@ -abstract class Parent(p: Raw[String]) { +abstract class Parent(p: Cold[String]) { val x = "name" lazy val z = bar def foo = bar def bar: Int } -class Child(o: Raw[String]) extends Parent(o) { +class Child(o: Cold[String]) extends Parent(o) { val y = "hello" val m = this.x diff --git a/tests/neg-custom-args/safe-init/parent7.scala b/tests/neg-custom-args/safe-init/parent7.scala index d7e3ff205d1a..e148e519a3a8 100644 --- a/tests/neg-custom-args/safe-init/parent7.scala +++ b/tests/neg-custom-args/safe-init/parent7.scala @@ -1,12 +1,12 @@ trait Foo { - @scala.annotation.raw + @scala.annotation.cold def name: String def title: String } trait Bar { this: Foo => - val message = "hello, " + name // ok: because `name` is marked raw + val message = "hello, " + name // ok: because `name` is marked cold println(title) // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/partial-select1.scala b/tests/neg-custom-args/safe-init/partial-select1.scala index a39129fde86a..b1f798f0773d 100644 --- a/tests/neg-custom-args/safe-init/partial-select1.scala +++ b/tests/neg-custom-args/safe-init/partial-select1.scala @@ -5,7 +5,7 @@ class Parent { val name = "parent" } -class Child(parent: Raw[Parent]) { +class Child(parent: Cold[Parent]) { val name = "child" var number = 0 diff --git a/tests/neg-custom-args/safe-init/simple-1f.scala b/tests/neg-custom-args/safe-init/simple-1f.scala index 2f10c48f9c7f..a6b42223b789 100644 --- a/tests/neg-custom-args/safe-init/simple-1f.scala +++ b/tests/neg-custom-args/safe-init/simple-1f.scala @@ -1,4 +1,4 @@ -class Foo(x: Raw[String]) { +class Foo(x: Cold[String]) { var name: String = _ name.size // error @@ -11,7 +11,7 @@ class Foo(x: Raw[String]) { name = x } -class Bar(x: Raw[String]) { +class Bar(x: Cold[String]) { var name: String = x name.size // error diff --git a/tests/neg-custom-args/safe-init/simple-1h.scala b/tests/neg-custom-args/safe-init/simple-1h.scala index 9be10b4229f7..97fa02187553 100644 --- a/tests/neg-custom-args/safe-init/simple-1h.scala +++ b/tests/neg-custom-args/safe-init/simple-1h.scala @@ -1,4 +1,4 @@ -class Foo(n: Raw[String]) { +class Foo(n: Cold[String]) { foo(new Foo("Jack")) // recursive creation val name: String = n diff --git a/tests/neg-custom-args/safe-init/simple-1i.scala b/tests/neg-custom-args/safe-init/simple-1i.scala index 6b523ebeb2ae..72fdf5881694 100644 --- a/tests/neg-custom-args/safe-init/simple-1i.scala +++ b/tests/neg-custom-args/safe-init/simple-1i.scala @@ -1,4 +1,4 @@ -class Foo(n: Raw[String]) { +class Foo(n: Cold[String]) { foo(new Foo("Jack")) // recursive creation val name: String = n From 51f3aff6fa05acfee34fe943797c22d01ed3d76d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 11:04:43 +0200 Subject: [PATCH 17/83] Change filled to warm --- .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../tools/dotc/transform/init/Checker.scala | 12 ++-- .../tools/dotc/transform/init/Heap.scala | 8 +-- .../tools/dotc/transform/init/Values.scala | 58 +++++++++---------- .../tools/dotc/transform/init/package.scala | 8 +-- library/src/dotty/DottyPredef.scala | 2 +- .../annotation/{filled.scala => warm.scala} | 8 +-- 7 files changed, 50 insertions(+), 50 deletions(-) rename library/src/scala/annotation/{filled.scala => warm.scala} (75%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2e3bc5c6f6c5..fad3f2f41385 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -808,8 +808,8 @@ class Definitions { def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass lazy val ColdAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.cold") def ColdAnnot(implicit ctx: Context): ClassSymbol = ColdAnnotType.symbol.asClass - lazy val FilledAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.filled") - def FilledAnnot(implicit ctx: Context): ClassSymbol = FilledAnnotType.symbol.asClass + lazy val WarmAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.warm") + def WarmAnnot(implicit ctx: Context): ClassSymbol = WarmAnnotType.symbol.asClass lazy val InitAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.init") def InitAnnot(implicit ctx: Context): ClassSymbol = InitAnnotType.symbol.asClass lazy val CallAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Call") diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 494cd5ef8ef4..16002f792b2c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -64,7 +64,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } def invalidImplementMsg(sym: Symbol) = { - val annot = if (sym.owner.is(Trait)) "raw" else "init" + val annot = if (sym.owner.is(Trait)) "cold" else "init" s"""|@scala.annotation.$annot required for ${sym.show} in ${sym.owner.show} |Because the method is called during initialization.""" .stripMargin @@ -98,8 +98,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val analyzer = new Analyzer - // raw check - rawCheck(cls, tmpl, analyzer) + // cold check + coldCheck(cls, tmpl, analyzer) // current class env needs special setup val root = Heap.createRootEnv @@ -129,7 +129,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (obj.open) initCheck(cls, obj, tmpl)(setting) } - def rawCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { + def coldCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { val obj = new ObjectValue(tp = cls.typeRef, open = !cls.is(Final) && !cls.isAnonymousClass) // enhancement possible to check if there are actual children // and whether children are possible in other modules. @@ -170,7 +170,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) if (res.hasErrors) { - ctx.warning("Forcing raw lazy value causes errors", sym.pos) + ctx.warning("Forcing cold lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { @@ -233,7 +233,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val actual = obj.select(sym, isStaticDispatch = true).value.widen()(setting.strict) if (actual == ColdValue) sym.annotate(defn.ColdAnnotType) - else if (actual == FilledValue) sym.annotate(defn.FilledAnnotType) + else if (actual == WarmValue) sym.annotate(defn.WarmAnnotType) obj.clearDynamicCalls() } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index e5f7bdb5eb65..a04d097d5153 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -216,7 +216,7 @@ class Env(outerId: Int) extends HeapEntry { } else if (sym.isClass && this.containsClass(sym.asClass)) Res() else { - // How do we know the class/method/field does not capture/use a raw/filled outer? + // How do we know the class/method/field does not capture/use a cold/warm outer? // If method/field exist, then the outer class beyond the method/field is full, // i.e. external methods/fields/classes are always safe. FullValue.select(sym) @@ -307,11 +307,11 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - def isColdOrFilled(value: Value): Boolean = - value == ColdValue || value == FilledValue + def isColdOrWarm(value: Value): Boolean = + value == ColdValue || value == WarmValue if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) ColdValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isCold || sym.info.isFilled) }) FilledValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isCold || sym.info.isWarm) }) WarmValue else { // check outer val owner = cls.owner diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 11e1c345c5f6..5392882780ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -62,7 +62,7 @@ sealed trait Value { /** Join two values * - * NoValue < Cold < Filled < Full + * NoValue < Cold < Warm < Full */ def join(other: Value): Value = (this, other) match { case (FullValue, v) => v @@ -99,7 +99,7 @@ sealed trait Value { val res = fv(i => FullValue, i => NoPosition)(setting2) if (res.hasErrors) { handler(res.effects) - FilledValue + WarmValue } else recur(res.value)(setting2) case sv: SliceValue => @@ -107,7 +107,7 @@ sealed trait Value { case ov: ObjectValue => if (ov.open) ColdValue else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => - if (acc != FullValue) return FilledValue + if (acc != FullValue) return WarmValue recur(v).join(acc) } case UnionValue(vs) => @@ -177,7 +177,7 @@ abstract sealed class OpaqueValue extends SingleValue { def <(that: OpaqueValue): Boolean = (this, that) match { case (FullValue, _) => false - case (FilledValue, ColdValue | FilledValue) => false + case (WarmValue, ColdValue | WarmValue) => false case (ColdValue, ColdValue) => false case _ => true } @@ -206,7 +206,7 @@ object FullValue extends OpaqueValue { if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(FullValue)) - if (args.exists(_.widen() < FullValue)) obj.add(cls, FilledValue) + if (args.exists(_.widen() < FullValue)) obj.add(cls, WarmValue) Res() } @@ -236,17 +236,17 @@ object BlankValue extends OpaqueValue { if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) + res += Generic(s"The $sym should be marked as `@cold` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { if (!sym.isCold) - res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) + res += Generic(s"The lazy field $sym should be marked as `@cold` in order to be accessed", setting.pos) } else if (sym.isClass) { if (!sym.isCold) - res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) + res += Generic(s"The nested $sym should be marked as `@cold` in order to be instantiated", setting.pos) } else { // field select res += Generic(s"The $sym may not be initialized", setting.pos) @@ -255,7 +255,7 @@ object BlankValue extends OpaqueValue { res } - /** assign to raw is always fine? */ + /** assign to cold is always fine? */ def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -265,12 +265,12 @@ object BlankValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isCold) { - res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) + res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) res.value = FullValue return res } - obj.add(cls, FilledValue) + obj.add(cls, WarmValue) Res() } @@ -280,7 +280,7 @@ object BlankValue extends OpaqueValue { override def toString = "blank value" } -/** A raw value, where class/trait params are initialized, but body fields are not +/** A cold value, where class/trait params are initialized, but body fields are not * */ object ColdValue extends OpaqueValue { @@ -290,17 +290,17 @@ object ColdValue extends OpaqueValue { if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@raw` in order to be called", setting.pos) + res += Generic(s"The $sym should be marked as `@cold` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { if (!sym.isCold) - res += Generic(s"The lazy field $sym should be marked as `@raw` in order to be accessed", setting.pos) + res += Generic(s"The lazy field $sym should be marked as `@cold` in order to be accessed", setting.pos) } else if (sym.isClass) { if (!sym.isCold) - res += Generic(s"The nested $sym should be marked as `@raw` in order to be instantiated", setting.pos) + res += Generic(s"The nested $sym should be marked as `@cold` in order to be instantiated", setting.pos) } else { // field select if (!sym.isClassParam) @@ -310,7 +310,7 @@ object ColdValue extends OpaqueValue { res } - /** assign to raw is always fine? */ + /** assign to cold is always fine? */ def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -320,22 +320,22 @@ object ColdValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isCold) { - res += Generic(s"The nested $cls should be marked as `@raw` in order to be instantiated", setting.pos) + res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) res.value = FullValue return res } - obj.add(cls, FilledValue) + obj.add(cls, WarmValue) Res() } def show(implicit setting: ShowSetting): String = "Cold" - override def toString = "raw value" + override def toString = "cold value" } -object FilledValue extends OpaqueValue { +object WarmValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { @@ -345,7 +345,7 @@ object FilledValue extends OpaqueValue { res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy) && !sym.isEffectiveInit) { - if (!sym.isCold && !sym.isFilled) + if (!sym.isCold && !sym.isWarm) res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) res.value = sym.info.value.join(sym.value) @@ -368,20 +368,20 @@ object FilledValue extends OpaqueValue { if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isCold && !cls.isFilled) { + if (!cls.isCold && !cls.isWarm) { res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) res.value = FullValue return res } - obj.add(cls, FilledValue) + obj.add(cls, WarmValue) Res() } - def show(implicit setting: ShowSetting): String = "Filled" + def show(implicit setting: ShowSetting): String = "Warm" - override def toString = "filled value" + override def toString = "warm value" } /** A function value or value of method select */ @@ -603,7 +603,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (sym.owner.is(Flags.Trait)) return ColdValue.select(sym) else - return FilledValue.select(sym) + return WarmValue.select(sym) } if (this.widen() == FullValue) return FullValue.select(sym) @@ -625,7 +625,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { else { // select on unknown super assert(target.isDefinedOn(tp)) - FilledValue.select(target) ++ res.effects + WarmValue.select(target) ++ res.effects } } @@ -642,7 +642,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { else { // select on unknown super assert(target.isDefinedOn(tp)) - FilledValue.assign(target, value) + WarmValue.assign(target, value) } } @@ -653,7 +653,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { slices(outerCls).init(constr, values, argPos, obj) } else { - val value = if (cls.isDefinedOn(tp)) FilledValue else ColdValue + val value = if (cls.isDefinedOn(tp)) WarmValue else ColdValue value.init(constr, values, argPos, obj) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 2aa786015caf..e1f795bd7277 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -25,11 +25,11 @@ package object init { implicit class TypeOps(val tp: Type) extends AnyVal { def isCold(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.ColdAnnot) - def isFilled(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.FilledAnnot) + def isWarm(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.WarmAnnot) def value(implicit ctx: Context) = if (isCold) ColdValue - else if (isFilled) FilledValue + else if (isWarm) WarmValue else FullValue } @@ -41,7 +41,7 @@ package object init { implicit class SymOps(val sym: Symbol) extends AnyVal { def isCold(implicit ctx: Context) = sym.hasAnnotation(defn.ColdAnnot) - def isFilled(implicit ctx: Context) = sym.hasAnnotation(defn.FilledAnnot) + def isWarm(implicit ctx: Context) = sym.hasAnnotation(defn.WarmAnnot) def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) @@ -65,7 +65,7 @@ package object init { def value(implicit ctx: Context) = if (isCold) ColdValue - else if (isFilled) FilledValue + else if (isWarm) WarmValue else FullValue def isConcreteField(implicit ctx: Context) = diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index b9f424be1b1f..eb80eea54389 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -42,5 +42,5 @@ object DottyPredef { @forceInline def locally[T](body: => T): T = body type Cold[T] = T @scala.annotation.cold - type Filled[T] = T @scala.annotation.filled + type Warm[T] = T @scala.annotation.warm } diff --git a/library/src/scala/annotation/filled.scala b/library/src/scala/annotation/warm.scala similarity index 75% rename from library/src/scala/annotation/filled.scala rename to library/src/scala/annotation/warm.scala index f77f214f1f7e..87d2b86ea784 100644 --- a/library/src/scala/annotation/filled.scala +++ b/library/src/scala/annotation/warm.scala @@ -4,15 +4,15 @@ package scala.annotation * under initialization, but all its fields are * assigned. * - * All fields of a filled object are assigned, but + * All fields of a warm object are assigned, but * the fields may refer to objects under initialization. * In contrast, a partial object may have unassigned * fields. * * When used on methods, it means `this` and `super` - * are filled. + * are warm. * * When used on constructors, it means the immediate - * outer is filled. + * outer is warm. */ -class filled extends StaticAnnotation +class warm extends StaticAnnotation From efb83950a8ca63aa3f0b5838ed3cecb74642284f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 11:10:12 +0200 Subject: [PATCH 18/83] Rename full to hot --- .../tools/dotc/transform/init/Analyzer.scala | 2 +- .../tools/dotc/transform/init/Checker.scala | 22 ++++---- .../tools/dotc/transform/init/Effects.scala | 2 +- .../tools/dotc/transform/init/Heap.scala | 10 ++-- .../tools/dotc/transform/init/Indexer.scala | 2 +- .../tools/dotc/transform/init/Values.scala | 52 +++++++++---------- .../tools/dotc/transform/init/package.scala | 4 +- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index d062b4820846..f1d05bb2bd20 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -253,7 +253,7 @@ class Analyzer extends Indexer { analyzer => val obj = new ObjectValue(tree.tpe, open = false) val res = checkInit(obj.tp, init, argss, obj) if (obj.slices.isEmpty) { - res.copy(value = FullValue) + res.copy(value = HotValue) } else { if (res.hasErrors) res.effects = Vector(Instantiate(tree.tpe.classSymbol, res.effects, tree.pos)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 16002f792b2c..4b5660f777f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -145,13 +145,13 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => else root.add(cls, ColdValue) val value = analyzer.methodValue(ddef)(setting.strict) - val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) + val res = value.apply(i => HotValue, i => NoPosition)(setting.strict) if (res.hasErrors) { ctx.warning("Calling the method during initialization causes errors", sym.pos) res.effects.foreach(_.report) } - else if (res.value != FullValue) { + else if (res.value != HotValue) { ctx.warning("A method called during initialization must return a fully initialized value", sym.pos) } } @@ -167,7 +167,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => else root.add(cls, ColdValue) val value = analyzer.lazyValue(vdef)(setting.strict) - val res = value.apply(i => FullValue, i => NoPosition)(setting.strict) + val res = value.apply(i => HotValue, i => NoPosition)(setting.strict) if (res.hasErrors) { ctx.warning("Forcing cold lazy value causes errors", sym.pos) @@ -175,7 +175,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } else { val value = res.value.widen()(setting.strict) - if (value != FullValue) ctx.warning("Cold lazy value must return a full value", sym.pos) + if (value != HotValue) ctx.warning("Cold lazy value must return a full value", sym.pos) } } @@ -195,15 +195,15 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => var res = obj.select(sym, isStaticDispatch = true) if (!sym.info.isParameterless) - res = res.value.apply(i => FullValue, i => NoPosition) + res = res.value.apply(i => HotValue, i => NoPosition) if (res.hasErrors) { setting.ctx.warning(s"Calling the init $sym causes errors", sym.pos) res.effects.foreach(_.report) } - else if (res.value != FullValue && !sym.isCalledIn(cls)) { // effective init + else if (res.value != HotValue && !sym.isCalledIn(cls)) { // effective init setting.ctx.warning("An init method must return a full value", sym.pos) } - else if (res.value == FullValue && sym.isCalledIn(cls)) { // de facto @init + else if (res.value == HotValue && sym.isCalledIn(cls)) { // de facto @init sym.annotate(defn.InitAnnotType) } @@ -221,7 +221,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } else { val value = res.value.widen()(setting.strict) - if (value != FullValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) + if (value != HotValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } obj.clearDynamicCalls() @@ -255,9 +255,9 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val outerValue = cls.value val enclosingCls = cls.owner.enclosingClass - if (!cls.owner.isClass || maxValue == FullValue) { - setting.env.add(enclosingCls, FullValue) - recur(enclosingCls, FullValue) + if (!cls.owner.isClass || maxValue == HotValue) { + setting.env.add(enclosingCls, HotValue) + recur(enclosingCls, HotValue) } else { val meet = outerValue.meet(maxValue) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index c36f74a2a25b..1706341ffc29 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -20,7 +20,7 @@ import collection.mutable //======================================= import Effect._ -case class Res(var effects: Effects = Vector.empty, var value: Value = FullValue) { +case class Res(var effects: Effects = Vector.empty, var value: Value = HotValue) { def +=(eff: Effect): Unit = effects = effects :+ eff def ++=(effs: Effects): Unit = diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index a04d097d5153..59ab68db3184 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -183,7 +183,7 @@ class Env(outerId: Int) extends HeapEntry { this(sym) = value Res() } - else if (value.widen() != FullValue) // leak assign + else if (value.widen() != HotValue) // leak assign Res(effects = Vector(Generic("Cannot leak an object under initialization", setting.pos))) else Res() @@ -217,9 +217,9 @@ class Env(outerId: Int) extends HeapEntry { else if (sym.isClass && this.containsClass(sym.asClass)) Res() else { // How do we know the class/method/field does not capture/use a cold/warm outer? - // If method/field exist, then the outer class beyond the method/field is full, + // If method/field exist, then the outer class beyond the method/field is hot, // i.e. external methods/fields/classes are always safe. - FullValue.select(sym) + HotValue.select(sym) } def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -228,7 +228,7 @@ class Env(outerId: Int) extends HeapEntry { val tmpl = this.getClassDef(cls) setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(this)) } - else FullValue.init(constr, values, argPos, obj) + else HotValue.init(constr, values, argPos, obj) } def show(implicit setting: ShowSetting): String = { @@ -315,7 +315,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo else { // check outer val owner = cls.owner - if (!owner.isClass) FullValue + if (!owner.isClass) HotValue else innerEnv(owner).widen() } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 9aea37f7868d..80a5e01f94a7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -95,7 +95,7 @@ trait Indexer { self: Analyzer => case vdef: ValDef if vdef.symbol.is(Lazy) => slice.add(vdef.symbol, lazyValue(vdef)(setting.withEnv(slice.innerEnv))) case vdef: ValDef => - val value = if (vdef.symbol.isInit || vdef.symbol.is(Deferred)) FullValue else NoValue + val value = if (vdef.symbol.isInit || vdef.symbol.is(Deferred)) HotValue else NoValue slice.add(vdef.symbol, value) case tdef: TypeDef if tdef.isClassDef => // class has to be handled differently because of inheritance diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 5392882780ec..7b0b7775fd23 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -24,7 +24,7 @@ import collection.mutable object Value { def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { paramInfos.zipWithIndex.foreach { case (tp, index) => - val value = scala.util.Try(values(index)).getOrElse(FullValue) + val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) if (value.widen() < tp.value) return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) @@ -34,7 +34,7 @@ object Value { def defaultFunctionValue(methSym: Symbol)(implicit setting: Setting): Value = { assert(methSym.is(Flags.Method)) - if (methSym.info.paramNamess.isEmpty) FullValue + if (methSym.info.paramNamess.isEmpty) HotValue else new FunctionValue() { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val paramInfos = methSym.info.paramInfoss.flatten @@ -62,11 +62,11 @@ sealed trait Value { /** Join two values * - * NoValue < Cold < Warm < Full + * NoValue < Cold < Warm < Hot */ def join(other: Value): Value = (this, other) match { - case (FullValue, v) => v - case (v, FullValue) => v + case (HotValue, v) => v + case (v, HotValue) => v case (NoValue, _) => NoValue case (_, NoValue) => NoValue case (BlankValue, _) => BlankValue @@ -96,7 +96,7 @@ sealed trait Value { case ov: OpaqueValue => ov case fv: FunctionValue => val setting2 = setting.freshHeap - val res = fv(i => FullValue, i => NoPosition)(setting2) + val res = fv(i => HotValue, i => NoPosition)(setting2) if (res.hasErrors) { handler(res.effects) WarmValue @@ -106,12 +106,12 @@ sealed trait Value { setting.heap(sv.id).asSlice.widen case ov: ObjectValue => if (ov.open) ColdValue - else ov.slices.values.foldLeft(FullValue: OpaqueValue) { (acc, v) => - if (acc != FullValue) return WarmValue + else ov.slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => + if (acc != HotValue) return WarmValue recur(v).join(acc) } case UnionValue(vs) => - vs.foldLeft(FullValue: OpaqueValue) { (acc, v) => + vs.foldLeft(HotValue: OpaqueValue) { (acc, v) => if (v == ColdValue || acc == ColdValue) return ColdValue else acc.join(recur(v)) } @@ -176,7 +176,7 @@ abstract sealed class OpaqueValue extends SingleValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? def <(that: OpaqueValue): Boolean = (this, that) match { - case (FullValue, _) => false + case (HotValue, _) => false case (WarmValue, ColdValue | WarmValue) => false case (ColdValue, ColdValue) => false case _ => true @@ -189,13 +189,13 @@ abstract sealed class OpaqueValue extends SingleValue { if (this < that) that else this } -object FullValue extends OpaqueValue { +object HotValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) else Res() def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen() != FullValue) + if (value.widen() != HotValue) Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", setting.pos))) else Res() @@ -205,15 +205,15 @@ object FullValue extends OpaqueValue { val res = Value.checkParams(cls, paramInfos, values, argPos) if (res.hasErrors) return res - val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(FullValue)) - if (args.exists(_.widen() < FullValue)) obj.add(cls, WarmValue) + val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) + if (args.exists(_.widen() < HotValue)) obj.add(cls, WarmValue) Res() } - def show(implicit setting: ShowSetting): String = "Full" + def show(implicit setting: ShowSetting): String = "Hot" - override def toString = "full value" + override def toString = "hot value" } /** A blank value, where class/trait params are not yet initialized @@ -231,8 +231,8 @@ object FullValue extends OpaqueValue { */ object BlankValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { - // set state to Full, don't report same error message again - val res = Res(value = FullValue) + // set state to Hot, don't report same error message again + val res = Res(value = HotValue) if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.name.is(DefaultGetterName)) @@ -266,7 +266,7 @@ object BlankValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isCold) { res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) - res.value = FullValue + res.value = HotValue return res } @@ -285,8 +285,8 @@ object BlankValue extends OpaqueValue { */ object ColdValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { - // set state to Full, don't report same error message again - val res = Res(value = FullValue) + // set state to Hot, don't report same error message again + val res = Res(value = HotValue) if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.name.is(DefaultGetterName)) @@ -321,7 +321,7 @@ object ColdValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isCold) { res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) - res.value = FullValue + res.value = HotValue return res } @@ -370,7 +370,7 @@ object WarmValue extends OpaqueValue { val cls = constr.owner.asClass if (!cls.isCold && !cls.isWarm) { res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) - res.value = FullValue + res.value = HotValue return res } @@ -447,7 +447,7 @@ abstract class FunctionValue extends SingleValue { self => val applySym = defn.FunctionClass(1).typeRef.member(nme.apply).symbol val res2 = fun(0).select(applySym) val res3 = res2.value.apply(res2.value :: Nil, argPos) - Res(value = FullValue, effects = res1.effects ++ res2.effects ++ res3.effects) + Res(value = HotValue, effects = res1.effects ++ res2.effects ++ res3.effects) } } Res(value = composedFun) @@ -472,7 +472,7 @@ abstract class FunctionValue extends SingleValue { self => } Res(value = selectedFun) case _ => - FullValue.select(sym) + HotValue.select(sym) } /** not supported */ @@ -606,7 +606,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { return WarmValue.select(sym) } - if (this.widen() == FullValue) return FullValue.select(sym) + if (this.widen() == HotValue) return HotValue.select(sym) val res = Res() diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index e1f795bd7277..818fff77ef9a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -30,7 +30,7 @@ package object init { def value(implicit ctx: Context) = if (isCold) ColdValue else if (isWarm) WarmValue - else FullValue + else HotValue } def calledSymsIn(cls: ClassSymbol)(implicit ctx: Context): List[Symbol] = @@ -66,7 +66,7 @@ package object init { def value(implicit ctx: Context) = if (isCold) ColdValue else if (isWarm) WarmValue - else FullValue + else HotValue def isConcreteField(implicit ctx: Context) = sym.isTerm && sym.is(AnyFlags, butNot = Deferred | Method | Local | Private) From c88dab257059439e2851ff20ff8c17652e39ffb2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 11:26:54 +0200 Subject: [PATCH 19/83] Refactor signature of widen --- .../tools/dotc/transform/init/Checker.scala | 6 +++--- .../dotty/tools/dotc/transform/init/Heap.scala | 4 ++-- .../tools/dotc/transform/init/Values.scala | 17 +++++++---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 4b5660f777f0..9536213761a8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -174,7 +174,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => res.effects.foreach(_.report) } else { - val value = res.value.widen()(setting.strict) + val value = res.value.widen(setting.strict) if (value != HotValue) ctx.warning("Cold lazy value must return a full value", sym.pos) } } @@ -220,7 +220,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => res.effects.foreach(_.report) } else { - val value = res.value.widen()(setting.strict) + val value = res.value.widen(setting.strict) if (value != HotValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } @@ -231,7 +231,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val sym = vdef.symbol if (sym.is(Flags.PrivateOrLocal)) return - val actual = obj.select(sym, isStaticDispatch = true).value.widen()(setting.strict) + val actual = obj.select(sym, isStaticDispatch = true).value.widen(setting.strict) if (actual == ColdValue) sym.annotate(defn.ColdAnnotType) else if (actual == WarmValue) sym.annotate(defn.WarmAnnotType) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 59ab68db3184..250a419fa93e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -183,7 +183,7 @@ class Env(outerId: Int) extends HeapEntry { this(sym) = value Res() } - else if (value.widen() != HotValue) // leak assign + else if (value.widen != HotValue) // leak assign Res(effects = Vector(Generic("Cannot leak an object under initialization", setting.pos))) else Res() @@ -316,7 +316,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo // check outer val owner = cls.owner if (!owner.isClass) HotValue - else innerEnv(owner).widen() + else innerEnv(owner).widen } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 7b0b7775fd23..0ae6f3e2a5e4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -26,7 +26,7 @@ object Value { paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) - if (value.widen() < tp.value) + if (value.widen < tp.value) return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) } Res() @@ -91,16 +91,13 @@ sealed trait Value { * * Widening is needed at analysis boundary. */ - def widen(handler: Vector[Effect] => Unit = effs => ())(implicit setting: Setting): OpaqueValue = { + def widen(implicit setting: Setting): OpaqueValue = { def recur(value: Value)(implicit setting: Setting): OpaqueValue = value match { case ov: OpaqueValue => ov case fv: FunctionValue => val setting2 = setting.freshHeap val res = fv(i => HotValue, i => NoPosition)(setting2) - if (res.hasErrors) { - handler(res.effects) - WarmValue - } + if (res.hasErrors) WarmValue else recur(res.value)(setting2) case sv: SliceValue => setting.heap(sv.id).asSlice.widen @@ -195,7 +192,7 @@ object HotValue extends OpaqueValue { else Res() def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen() != HotValue) + if (value.widen != HotValue) Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", setting.pos))) else Res() @@ -206,7 +203,7 @@ object HotValue extends OpaqueValue { if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) - if (args.exists(_.widen() < HotValue)) obj.add(cls, WarmValue) + if (args.exists(_.widen < HotValue)) obj.add(cls, WarmValue) Res() } @@ -358,7 +355,7 @@ object WarmValue extends OpaqueValue { } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen() < sym.info.value) + if (value.widen < sym.info.value) Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", setting.pos))) else Res() @@ -606,7 +603,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { return WarmValue.select(sym) } - if (this.widen() == HotValue) return HotValue.select(sym) + if (this.widen == HotValue) return HotValue.select(sym) val res = Res() From ab33a927727f6c5fbdf8e3e71a3820d6c2fe0a2e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 11:29:08 +0200 Subject: [PATCH 20/83] Rename BlankValue to IcyValue --- .../src/dotty/tools/dotc/transform/init/Checker.scala | 4 ++-- .../src/dotty/tools/dotc/transform/init/Values.scala | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 9536213761a8..bb8e1e2072fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -141,7 +141,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isCold) root.add(cls, BlankValue) + if (sym.isCold) root.add(cls, IcyValue) else root.add(cls, ColdValue) val value = analyzer.methodValue(ddef)(setting.strict) @@ -163,7 +163,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer) indexOuter(cls)(setting) - if (sym.isCold) root.add(cls, BlankValue) + if (sym.isCold) root.add(cls, IcyValue) else root.add(cls, ColdValue) val value = analyzer.lazyValue(vdef)(setting.strict) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 0ae6f3e2a5e4..706b8dbf4510 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -69,8 +69,8 @@ sealed trait Value { case (v, HotValue) => v case (NoValue, _) => NoValue case (_, NoValue) => NoValue - case (BlankValue, _) => BlankValue - case (_, BlankValue) => BlankValue + case (IcyValue, _) => IcyValue + case (_, IcyValue) => IcyValue case (ColdValue, _) => ColdValue case (_, ColdValue) => ColdValue case (v1: OpaqueValue, v2: OpaqueValue) => v1.join(v2) @@ -213,7 +213,7 @@ object HotValue extends OpaqueValue { override def toString = "hot value" } -/** A blank value, where class/trait params are not yet initialized +/** An icy value, where class/trait params are not yet initialized * * abstract class A { * def f: Int @@ -226,7 +226,7 @@ object HotValue extends OpaqueValue { * * class C extends A with B(20) */ -object BlankValue extends OpaqueValue { +object IcyValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { // set state to Hot, don't report same error message again val res = Res(value = HotValue) @@ -274,7 +274,7 @@ object BlankValue extends OpaqueValue { def show(implicit setting: ShowSetting): String = "Cold" - override def toString = "blank value" + override def toString = "icy value" } /** A cold value, where class/trait params are initialized, but body fields are not From 93c90ca0821775df13e8cc275a52ccd0cfde9872 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 11:40:17 +0200 Subject: [PATCH 21/83] WIP - refactor widen --- .../tools/dotc/transform/init/Values.scala | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 706b8dbf4510..f53345e12661 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -91,7 +91,8 @@ sealed trait Value { * * Widening is needed at analysis boundary. */ - def widen(implicit setting: Setting): OpaqueValue = { + def widen(implicit setting: Setting): OpaqueValue + def recur(value: Value)(implicit setting: Setting): OpaqueValue = value match { case ov: OpaqueValue => ov case fv: FunctionValue => @@ -116,9 +117,6 @@ sealed trait Value { case _ => // impossible ??? } - - recur(this) - } } /** The value is absent */ @@ -127,6 +125,7 @@ object NoValue extends Value { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = ??? def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? + def widen(implicit setting: Setting): OpaqueValue = ??? def show(implicit setting: ShowSetting): String = "NoValue" } @@ -160,6 +159,12 @@ case class UnionValue(val values: Set[SingleValue]) extends Value { } } + def widen(implicit setting: Setting): OpaqueValue = + values.foldLeft(HotValue: OpaqueValue) { (acc, v) => + if (v == ColdValue || acc == ColdValue) return ColdValue + else acc.join(recur(v)) + } + def +(value: SingleValue): UnionValue = UnionValue(values + value) def ++(uv: UnionValue): UnionValue = UnionValue(values ++ uv.values) @@ -174,8 +179,9 @@ abstract sealed class OpaqueValue extends SingleValue { def <(that: OpaqueValue): Boolean = (this, that) match { case (HotValue, _) => false - case (WarmValue, ColdValue | WarmValue) => false - case (ColdValue, ColdValue) => false + case (WarmValue, ColdValue | WarmValue | IcyValue) => false + case (ColdValue, ColdValue | IcyValue) => false + case (IcyValue, IcyValue) => false case _ => true } @@ -184,6 +190,8 @@ abstract sealed class OpaqueValue extends SingleValue { def meet(that: OpaqueValue): OpaqueValue = if (this < that) that else this + + def widen(implicit setting: Setting): OpaqueValue = this } object HotValue extends OpaqueValue { @@ -503,6 +511,9 @@ abstract class LazyValue extends SingleValue { def show(implicit setting: ShowSetting): String = toString override def toString: String = "LazyValue@" + hashCode + + // impossible + def widen(implicit setting: Setting): OpaqueValue = ??? } /** A slice of an object */ @@ -552,6 +563,8 @@ class SliceValue(val id: Int) extends SingleValue { setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) } + def widen(implicit setting: Setting): OpaqueValue = setting.heap(id).asSlice.widen + override def hashCode = id override def equals(that: Any) = that match { @@ -655,6 +668,13 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } } + def widen(implicit setting: Setting): OpaqueValue = + if (open) ColdValue + else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => + if (acc != HotValue) return WarmValue + v.widen.join(acc) + } + def show(implicit setting: ShowSetting): String = { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") "Object {\n" + setting.indent(body) + "\n}" From 418c37fd2c639e2608557476381d2ab30e8b36c1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 14:59:35 +0200 Subject: [PATCH 22/83] Refactor sym.isCold and sym.isWarm --- compiler/src/dotty/tools/dotc/transform/init/Heap.scala | 5 +---- compiler/src/dotty/tools/dotc/transform/init/Values.scala | 6 +++--- compiler/src/dotty/tools/dotc/transform/init/package.scala | 6 ++++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 250a419fa93e..9cf27e7c1506 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,11 +307,8 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - def isColdOrWarm(value: Value): Boolean = - value == ColdValue || value == WarmValue - if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) ColdValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.info.isCold || sym.info.isWarm) }) WarmValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.isCold || sym.isWarm) }) WarmValue else { // check outer val owner = cls.owner diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index f53345e12661..7d90a87f6a64 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -353,17 +353,17 @@ object WarmValue extends OpaqueValue { if (!sym.isCold && !sym.isWarm) res += Generic(s"The lazy field $sym should be marked as `@init` in order to be accessed", setting.pos) - res.value = sym.info.value.join(sym.value) + res.value = sym.value } else { - res.value = sym.info.value.join(sym.value) + res.value = sym.value } res } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen < sym.info.value) + if (value.widen < sym.value) Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", setting.pos))) else Res() diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 818fff77ef9a..c7777a269bd7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -39,9 +39,11 @@ package object init { } implicit class SymOps(val sym: Symbol) extends AnyVal { - def isCold(implicit ctx: Context) = sym.hasAnnotation(defn.ColdAnnot) + def isCold(implicit ctx: Context) = + sym.hasAnnotation(defn.ColdAnnot) || sym.info.isCold - def isWarm(implicit ctx: Context) = sym.hasAnnotation(defn.WarmAnnot) + def isWarm(implicit ctx: Context) = + sym.hasAnnotation(defn.WarmAnnot) || sym.info.isWarm def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) From 8e500ce48dcc0b005e1564cd6a97054ae8f2a351 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 15:48:12 +0200 Subject: [PATCH 23/83] Make WarmValue condition on dependencies --- .../tools/dotc/transform/init/Checker.scala | 12 +-- .../tools/dotc/transform/init/Heap.scala | 2 +- .../tools/dotc/transform/init/Values.scala | 102 +++++++----------- .../tools/dotc/transform/init/package.scala | 4 +- 4 files changed, 46 insertions(+), 74 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index bb8e1e2072fa..517e84c81c23 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -175,7 +175,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } else { val value = res.value.widen(setting.strict) - if (value != HotValue) ctx.warning("Cold lazy value must return a full value", sym.pos) + if (!value.isHot) ctx.warning("Cold lazy value must return a full value", sym.pos) } } @@ -200,10 +200,10 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => setting.ctx.warning(s"Calling the init $sym causes errors", sym.pos) res.effects.foreach(_.report) } - else if (res.value != HotValue && !sym.isCalledIn(cls)) { // effective init + else if (!res.value.isHot && !sym.isCalledIn(cls)) { // effective init setting.ctx.warning("An init method must return a full value", sym.pos) } - else if (res.value == HotValue && sym.isCalledIn(cls)) { // de facto @init + else if (res.value.isHot && sym.isCalledIn(cls)) { // de facto @init sym.annotate(defn.InitAnnotType) } @@ -221,7 +221,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } else { val value = res.value.widen(setting.strict) - if (value != HotValue) setting.ctx.warning("Init lazy value must return a full value", sym.pos) + if (!value.isHot) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } obj.clearDynamicCalls() @@ -232,8 +232,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (sym.is(Flags.PrivateOrLocal)) return val actual = obj.select(sym, isStaticDispatch = true).value.widen(setting.strict) - if (actual == ColdValue) sym.annotate(defn.ColdAnnotType) - else if (actual == WarmValue) sym.annotate(defn.WarmAnnotType) + if (actual.isCold) sym.annotate(defn.ColdAnnotType) + else if (actual.isWarm) sym.annotate(defn.WarmAnnotType) obj.clearDynamicCalls() } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 9cf27e7c1506..0e18f9a3fddf 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -308,7 +308,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo def widen(implicit setting: Setting): OpaqueValue = { if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) ColdValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.isCold || sym.isWarm) }) WarmValue + else if (symbols.exists { case (sym, value) => sym.isField && (sym.isCold || sym.isWarm) }) WarmValue() else { // check outer val owner = cls.owner diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 7d90a87f6a64..531f55f7caeb 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -93,30 +93,10 @@ sealed trait Value { */ def widen(implicit setting: Setting): OpaqueValue - def recur(value: Value)(implicit setting: Setting): OpaqueValue = value match { - case ov: OpaqueValue => ov - case fv: FunctionValue => - val setting2 = setting.freshHeap - val res = fv(i => HotValue, i => NoPosition)(setting2) - if (res.hasErrors) WarmValue - else recur(res.value)(setting2) - case sv: SliceValue => - setting.heap(sv.id).asSlice.widen - case ov: ObjectValue => - if (ov.open) ColdValue - else ov.slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - if (acc != HotValue) return WarmValue - recur(v).join(acc) - } - case UnionValue(vs) => - vs.foldLeft(HotValue: OpaqueValue) { (acc, v) => - if (v == ColdValue || acc == ColdValue) return ColdValue - else acc.join(recur(v)) - } - // case NoValue => NoValue - case _ => // impossible - ??? - } + def isIcy: Boolean = this == IcyValue + def isCold: Boolean = this == ColdValue + def isWarm: Boolean = this.isInstanceOf[WarmValue] + def isHot: Boolean = this == HotValue } /** The value is absent */ @@ -177,19 +157,23 @@ abstract sealed class OpaqueValue extends SingleValue { // not supported def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? - def <(that: OpaqueValue): Boolean = (this, that) match { - case (HotValue, _) => false - case (WarmValue, ColdValue | WarmValue | IcyValue) => false - case (ColdValue, ColdValue | IcyValue) => false - case (IcyValue, IcyValue) => false - case _ => true + def join(that: OpaqueValue): OpaqueValue = (this, that) match { + case (_, IcyValue) | (IcyValue, _) => IcyValue + case (ColdValue, _) | (_, ColdValue) => ColdValue + case (WarmValue(deps1), WarmValue(deps2)) => WarmValue(deps1 ++ deps2) + case (w: WarmValue, _) => w + case (_, w: WarmValue) => w + case _ => HotValue } - def join(that: OpaqueValue): OpaqueValue = - if (this < that) this else that - - def meet(that: OpaqueValue): OpaqueValue = - if (this < that) that else this + def meet(that: OpaqueValue): OpaqueValue = (this, that) match { + case (_, HotValue) | (HotValue, _) => HotValue + case (WarmValue(deps1), WarmValue(deps2)) => WarmValue(deps1 & deps2) + case (w: WarmValue, _) => w + case (_, w: WarmValue) => w + case (ColdValue, _) | (_, ColdValue) => ColdValue + case _ => IcyValue + } def widen(implicit setting: Setting): OpaqueValue = this } @@ -211,7 +195,7 @@ object HotValue extends OpaqueValue { if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) - if (args.exists(_.widen < HotValue)) obj.add(cls, WarmValue) + if (args.exists(_.widen < HotValue)) obj.add(cls, WarmValue()) Res() } @@ -260,27 +244,11 @@ object IcyValue extends OpaqueValue { res } - /** assign to cold is always fine? */ - def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() - - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos) - if (res.hasErrors) return res - - val cls = constr.owner.asClass - if (!cls.isCold) { - res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) - res.value = HotValue - return res - } - - obj.add(cls, WarmValue) + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? - Res() - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? - def show(implicit setting: ShowSetting): String = "Cold" + def show(implicit setting: ShowSetting): String = "Icy" override def toString = "icy value" } @@ -330,7 +298,7 @@ object ColdValue extends OpaqueValue { return res } - obj.add(cls, WarmValue) + obj.add(cls, WarmValue()) Res() } @@ -340,7 +308,13 @@ object ColdValue extends OpaqueValue { override def toString = "cold value" } -object WarmValue extends OpaqueValue { +/** A warm value has all its fields assigned. + * + * A warm value is not fully initialized, as it may depend on fields or methods of cold/warm values. + * + * If `deps.isEmpty`, then the value has unknown dependencies. + */ +case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { @@ -379,7 +353,7 @@ object WarmValue extends OpaqueValue { return res } - obj.add(cls, WarmValue) + obj.add(cls, WarmValue()) Res() } @@ -613,11 +587,9 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (sym.owner.is(Flags.Trait)) return ColdValue.select(sym) else - return WarmValue.select(sym) + return WarmValue().select(sym) } - if (this.widen == HotValue) return HotValue.select(sym) - val res = Res() // remember dynamic calls @@ -635,7 +607,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { else { // select on unknown super assert(target.isDefinedOn(tp)) - WarmValue.select(target) ++ res.effects + WarmValue().select(target) ++ res.effects } } @@ -652,7 +624,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { else { // select on unknown super assert(target.isDefinedOn(tp)) - WarmValue.assign(target, value) + WarmValue().assign(target, value) } } @@ -663,7 +635,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { slices(outerCls).init(constr, values, argPos, obj) } else { - val value = if (cls.isDefinedOn(tp)) WarmValue else ColdValue + val value = if (cls.isDefinedOn(tp)) WarmValue() else ColdValue value.init(constr, values, argPos, obj) } } @@ -671,7 +643,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { def widen(implicit setting: Setting): OpaqueValue = if (open) ColdValue else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - if (acc != HotValue) return WarmValue + if (acc != HotValue) return WarmValue() v.widen.join(acc) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index c7777a269bd7..a43762556868 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -29,7 +29,7 @@ package object init { def value(implicit ctx: Context) = if (isCold) ColdValue - else if (isWarm) WarmValue + else if (isWarm) WarmValue() else HotValue } @@ -67,7 +67,7 @@ package object init { def value(implicit ctx: Context) = if (isCold) ColdValue - else if (isWarm) WarmValue + else if (isWarm) WarmValue() else HotValue def isConcreteField(implicit ctx: Context) = From 079e9b4d7e7eb5c358c52122340653c35a5f83ee Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 15:54:36 +0200 Subject: [PATCH 24/83] Leak warm objects as cold --- compiler/src/dotty/tools/dotc/transform/init/Values.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 531f55f7caeb..b9669d8cab48 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -26,7 +26,7 @@ object Value { paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) - if (value.widen < tp.value) + if (!value.widen.isHot && !tp.value.isCold) // warm objects only leak as cold, for safety and simplicity return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) } Res() @@ -142,7 +142,7 @@ case class UnionValue(val values: Set[SingleValue]) extends Value { def widen(implicit setting: Setting): OpaqueValue = values.foldLeft(HotValue: OpaqueValue) { (acc, v) => if (v == ColdValue || acc == ColdValue) return ColdValue - else acc.join(recur(v)) + else acc.join(v.widen) } def +(value: SingleValue): UnionValue = UnionValue(values + value) @@ -195,7 +195,7 @@ object HotValue extends OpaqueValue { if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) - if (args.exists(_.widen < HotValue)) obj.add(cls, WarmValue()) + if (args.exists(!_.widen.isHot)) obj.add(cls, WarmValue()) Res() } @@ -337,7 +337,7 @@ case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen < sym.value) + if (!value.widen.isHot && !sym.isCold) Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", setting.pos))) else Res() From 9a882ec1ecd52a7bdd1cd85e0e426009197c0aa6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 16:25:08 +0200 Subject: [PATCH 25/83] Prevent leak of icy objects --- .../tools/dotc/transform/init/Values.scala | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index b9669d8cab48..e5811c513142 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -26,8 +26,9 @@ object Value { paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) - if (!value.widen.isHot && !tp.value.isCold) // warm objects only leak as cold, for safety and simplicity - return Res(effects = Vector(Generic("Leak of object under initialization to " + sym.show, pos))) + val wValue = value.widen + if (!wValue.isHot && !tp.value.isCold || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity + return Res(effects = Vector(Generic("Unsafe leak of object under initialization to " + sym.show, pos))) } Res() } @@ -105,7 +106,7 @@ object NoValue extends Value { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = ??? def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? - def widen(implicit setting: Setting): OpaqueValue = ??? + def widen(implicit setting: Setting): OpaqueValue = ColdValue def show(implicit setting: ShowSetting): String = "NoValue" } @@ -141,8 +142,7 @@ case class UnionValue(val values: Set[SingleValue]) extends Value { def widen(implicit setting: Setting): OpaqueValue = values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - if (v == ColdValue || acc == ColdValue) return ColdValue - else acc.join(v.widen) + acc.join(v.widen) } def +(value: SingleValue): UnionValue = UnionValue(values + value) @@ -336,10 +336,12 @@ case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { res } - def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (!value.widen.isHot && !sym.isCold) + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { + val wValue = value.widen + if (!wValue.isHot && !sym.isCold || wValue.isIcy) Res(effects = Vector(Generic("Cannot assign an object of a lower state to a field of higher state", setting.pos))) else Res() + } def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val paramInfos = constr.info.paramInfoss.flatten @@ -485,9 +487,6 @@ abstract class LazyValue extends SingleValue { def show(implicit setting: ShowSetting): String = toString override def toString: String = "LazyValue@" + hashCode - - // impossible - def widen(implicit setting: Setting): OpaqueValue = ??? } /** A slice of an object */ @@ -537,7 +536,8 @@ class SliceValue(val id: Int) extends SingleValue { setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) } - def widen(implicit setting: Setting): OpaqueValue = setting.heap(id).asSlice.widen + def widen(implicit setting: Setting): OpaqueValue = + setting.heap(id).asSlice.widen override def hashCode = id @@ -643,7 +643,6 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { def widen(implicit setting: Setting): OpaqueValue = if (open) ColdValue else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - if (acc != HotValue) return WarmValue() v.widen.join(acc) } From 63c5d1faf6e56f496dca38ec88e8160b2bd5f624 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 24 Oct 2018 16:57:31 +0200 Subject: [PATCH 26/83] WIP - scaffold for capture analysis --- .../tools/dotc/transform/init/Analyzer.scala | 6 +++++ .../tools/dotc/transform/init/Heap.scala | 10 +++----- .../tools/dotc/transform/init/Indexer.scala | 10 ++++++++ .../tools/dotc/transform/init/Values.scala | 13 +++++++++++ tests/neg-custom-args/safe-init/inner12.scala | 23 +++++++++++++++++++ .../safe-init/override12.scala | 13 +++++++++++ .../safe-init/override13.scala | 13 +++++++++++ 7 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/inner12.scala create mode 100644 tests/neg-custom-args/safe-init/override12.scala create mode 100644 tests/neg-custom-args/safe-init/override13.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index f1d05bb2bd20..307597c2603e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -103,6 +103,12 @@ class Analyzer extends Indexer { analyzer => def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { analyzer.apply(body) } + + def widen(implicit setting: Setting) = { + val res = analyzer.apply(body) + if (res.hasErrors) ColdValue + else res.value.widen + } } val thenFun = makeFun(thenp) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 0e18f9a3fddf..f1d37ec1ea40 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,13 +307,9 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - if (symbols.exists { case (sym, value) => sym.isField && value == NoValue }) ColdValue - else if (symbols.exists { case (sym, value) => sym.isField && (sym.isCold || sym.isWarm) }) WarmValue() - else { - // check outer - val owner = cls.owner - if (!owner.isClass) HotValue - else innerEnv(owner).widen + _syms.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => + acc.join(v.widen) } + // TODO: check inner classes } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 80a5e01f94a7..592a0f028fc9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -49,6 +49,11 @@ trait Indexer { self: Analyzer => res } } + + def widen(implicit setting2: Setting) = { + // capture analysis + WarmValue() + } } def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = @@ -69,6 +74,11 @@ trait Indexer { self: Analyzer => res } } + + def widen(implicit setting2: Setting) = { + // capture analysis + WarmValue() + } } /** Index local definitions */ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index e5811c513142..884173926a45 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -41,6 +41,8 @@ object Value { val paramInfos = methSym.info.paramInfoss.flatten checkParams(methSym, paramInfos, values, argPos) } + + def widen(implicit setting: Setting) = HotValue } } } @@ -383,9 +385,11 @@ abstract class FunctionValue extends SingleValue { self => val res3 = self.apply(res2.value :: Nil, argPos) Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) } + def widen(implicit setting: Setting) = fun(0).widen.join(self.widen) } Res(value = composedFun) } + def widen(implicit setting: Setting) = ??? } Res(value = selectedFun) case nme.andThen => @@ -400,9 +404,11 @@ abstract class FunctionValue extends SingleValue { self => val res3 = res2.value.apply(res2.value :: Nil, argPos) Res(value = res3.value, effects = res1.effects ++ res2.effects ++ res3.effects) } + def widen(implicit setting: Setting) = fun(0).widen.join(self.widen) } Res(value = composedFun) } + def widen(implicit setting: Setting) = ??? } Res(value = selectedFun) case nme.applyOrElse => @@ -416,6 +422,7 @@ abstract class FunctionValue extends SingleValue { self => val res3 = res2.value.apply(arg :: Nil, argPos) Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) } + def widen(implicit setting: Setting) = ??? } Res(value = selectedFun) case nme.runWith => @@ -430,9 +437,11 @@ abstract class FunctionValue extends SingleValue { self => val res3 = res2.value.apply(res2.value :: Nil, argPos) Res(value = HotValue, effects = res1.effects ++ res2.effects ++ res3.effects) } + def widen(implicit setting: Setting) = fun(0).widen.join(self.widen) } Res(value = composedFun) } + def widen(implicit setting: Setting) = ??? } Res(value = selectedFun) case nme.orElse => @@ -447,9 +456,11 @@ abstract class FunctionValue extends SingleValue { self => val res3 = res2.value.apply(arg :: Nil, argPos) Res(value = res1.value.join(res3.value), effects = res1.effects ++ res2.effects ++ res3.effects) } + def widen(implicit setting: Setting) = fun(0).widen.join(self.widen) } Res(value = composedFun) } + def widen(implicit setting: Setting) = ??? } Res(value = selectedFun) case _ => @@ -473,6 +484,8 @@ abstract class FunctionValue extends SingleValue { self => setting.heap.join(setting2.heap) res1.join(res2) } + + def widen(implicit setting: Setting) = that.widen.join(self.widen) } } diff --git a/tests/neg-custom-args/safe-init/inner12.scala b/tests/neg-custom-args/safe-init/inner12.scala new file mode 100644 index 000000000000..7b394be18cb6 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner12.scala @@ -0,0 +1,23 @@ +object A { + val x = 10 + + class Inner { + val y = x + + def f(n: Int) = x * n + } + + println(new Inner) // ok +} + +object B { + val x = 10 + + case class Inner(m: Int) { + def f(n: Int) = z * n + } + + println(Inner(x)) // error + + val z = 10 +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override12.scala b/tests/neg-custom-args/safe-init/override12.scala new file mode 100644 index 000000000000..ad0db3710720 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override12.scala @@ -0,0 +1,13 @@ +class A { + val b = new B(this) + println(b.m.f) // error + + val x = 10 + + @scala.annotation.init + def f: Int = x +} + +class B(a: Cold[A]) { + val m: A = a +} diff --git a/tests/neg-custom-args/safe-init/override13.scala b/tests/neg-custom-args/safe-init/override13.scala new file mode 100644 index 000000000000..09dbdc31d389 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override13.scala @@ -0,0 +1,13 @@ +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 +} From 3568ec4559d4f35bcc45933b68f672be7442a054 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 25 Oct 2018 16:49:08 +0200 Subject: [PATCH 27/83] WIP - initial capture analysis --- .../tools/dotc/transform/init/Analyzer.scala | 7 +- .../tools/dotc/transform/init/Capture.scala | 73 +++++++++++++++++++ .../tools/dotc/transform/init/package.scala | 3 + 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Capture.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 307597c2603e..0f2f5b983aa0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -66,11 +66,8 @@ class Analyzer extends Indexer { analyzer => res } - private def enclosedIn(curSym: Symbol, inSym: Symbol)(implicit setting: Setting): Boolean = - curSym.exists && ((curSym `eq` inSym) || (enclosedIn(curSym.owner, inSym))) - def checkRef(tp: Type)(implicit setting: Setting): Res = trace("checking " + tp.show)(tp match { - case tp : TermRef if tp.symbol.is(Module) && enclosedIn(setting.ctx.owner, tp.symbol.moduleClass) => + case tp : TermRef if tp.symbol.is(Module) && setting.ctx.owner.enclosedIn(tp.symbol.moduleClass) => // self reference by name: object O { ... O.xxx } checkRef(ThisType.raw(tp.symbol.moduleClass.typeRef)) case tp @ TermRef(NoPrefix, _) => @@ -85,7 +82,7 @@ class Analyzer extends Indexer { analyzer => else { // ThisType used outside of class scope, can happen for objects // see tests/pos/t2712-7.scala - assert(cls.is(Flags.Module) && !enclosedIn(setting.ctx.owner, cls)) + assert(cls.is(Flags.Module) && !setting.ctx.owner.enclosedIn(cls)) Res() } }) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala new file mode 100644 index 000000000000..f36fc3e68ece --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala @@ -0,0 +1,73 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import MegaPhase._ +import Contexts.Context +import StdNames._ +import Names._ +import Phases._ +import ast._ +import tpd._ +import Flags._ +import SymUtils._ +import Symbols._ +import Denotations._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import util.Positions._ +import config.Printers.init.{ println => debug } +import Constants.Constant +import collection.mutable + +object Capture { + private class CaptureTraverser(inner: Symbol, outer: Symbol)(implicit ctx: Context) extends TreeTraverser { + val captured: mutable.Set[Type] = mutable.Set.empty + + /** outer is inclusive, inner is exclusive */ + def within(sym: Symbol)(implicit ctx: Context): Boolean = + sym.exists && sym != inner && (sym.owner == outer || within(sym.owner)) + + def within(tp: Type)(implicit ctx: Context): Boolean = tp match { + case tp : TermRef if tp.symbol.is(Module) && ctx.owner.enclosedIn(tp.symbol.moduleClass) => + // self reference by name: object O { ... O.xxx } + within(ThisType.raw(tp.symbol.moduleClass.typeRef)) + case tp @ TermRef(NoPrefix, _) => + within(tp.symbol) + case tp @ TermRef(prefix, _) => + within(prefix) + case tp @ ThisType(tref) => + within(tref.symbol) + case _ => false + } + + def check(tp: Type) = if (within(tp)) captured += tp + + def traverse(tree: Tree)(implicit ctx: Context) = try { //debug + def enclosure = ctx.owner.enclosingMethod + + tree match { + case tree: Ident => + check(tree.tpe) + case Trees.Select(ths: This, _) => + check(tree.tpe) + case tree: Select => + traverseChildren(tree.qualifier) + case tree: This => + captured += tree.tpe + case _ => + traverseChildren(tree) + } + } catch { //debug + case ex: Exception => + println(i"$ex while traversing $tree") + throw ex + } + } + + def analyze(inner: Symbol, outer: Symbol)(implicit setting: Setting): Set[Type] = + new CaptureTraverser(inner, outer).captured.toSet +} diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index a43762556868..ef28ad3e322e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -81,6 +81,9 @@ package object init { def annotate(tp: Type)(implicit ctx: Context) = sym.addAnnotation(Annotations.ConcreteAnnotation(tpd.New(tp, Nil))) + + def enclosedIn(inSym: Symbol)(implicit ctx: Context): Boolean = + sym.exists && ((sym `eq` inSym) || sym.owner.enclosedIn(inSym)) } implicit def setting2ctx(implicit s: Setting): Context = s.ctx From ef3a64704eddff30c1b18a00ff4b4835841b2418 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 25 Oct 2018 18:27:46 +0200 Subject: [PATCH 28/83] WIP - capture analysis proto works --- .../tools/dotc/transform/init/Capture.scala | 40 +++++++++++-------- .../tools/dotc/transform/init/Heap.scala | 3 +- .../tools/dotc/transform/init/Indexer.scala | 19 +++++++-- .../tools/dotc/transform/init/Values.scala | 12 ++++-- tests/neg-custom-args/safe-init/inner12.scala | 7 +++- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala index f36fc3e68ece..f3ced2b559b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala @@ -24,40 +24,43 @@ import Constants.Constant import collection.mutable object Capture { - private class CaptureTraverser(inner: Symbol, outer: Symbol)(implicit ctx: Context) extends TreeTraverser { + private class CaptureTraverser(setting: Setting) extends TreeTraverser { val captured: mutable.Set[Type] = mutable.Set.empty - /** outer is inclusive, inner is exclusive */ - def within(sym: Symbol)(implicit ctx: Context): Boolean = - sym.exists && sym != inner && (sym.owner == outer || within(sym.owner)) + def trace[T](msg: => String)(body: => T) = { + println(msg) + val res = body + println(msg + s" = $res" ) + res + } - def within(tp: Type)(implicit ctx: Context): Boolean = tp match { + def free(tp: Type)(implicit ctx: Context): Boolean = (tp match { case tp : TermRef if tp.symbol.is(Module) && ctx.owner.enclosedIn(tp.symbol.moduleClass) => // self reference by name: object O { ... O.xxx } - within(ThisType.raw(tp.symbol.moduleClass.typeRef)) + free(ThisType.raw(tp.symbol.moduleClass.typeRef)) case tp @ TermRef(NoPrefix, _) => - within(tp.symbol) + setting.env.contains(tp.symbol) case tp @ TermRef(prefix, _) => - within(prefix) + free(prefix) case tp @ ThisType(tref) => - within(tref.symbol) + val cls = tref.symbol + !cls.is(Package) && setting.env.contains(cls) case _ => false - } + }) - def check(tp: Type) = if (within(tp)) captured += tp + def check(tp: Type)(implicit ctx: Context) = if (free(tp)) captured += tp def traverse(tree: Tree)(implicit ctx: Context) = try { //debug - def enclosure = ctx.owner.enclosingMethod - tree match { case tree: Ident => check(tree.tpe) case Trees.Select(ths: This, _) => check(tree.tpe) case tree: Select => - traverseChildren(tree.qualifier) + traverse(tree.qualifier) case tree: This => - captured += tree.tpe + check(tree.tpe) + case tree if tree.isType =>// ignore all type trees case _ => traverseChildren(tree) } @@ -68,6 +71,9 @@ object Capture { } } - def analyze(inner: Symbol, outer: Symbol)(implicit setting: Setting): Set[Type] = - new CaptureTraverser(inner, outer).captured.toSet + def analyze(tree: Tree)(implicit setting: Setting): Set[Type] = { + val cap = new CaptureTraverser(setting) + cap.traverse(tree) + cap.captured.toSet + } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index f1d37ec1ea40..bdecd5fee2f6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,8 +307,9 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { + val setting2 = setting.withEnv(innerEnv.outer) _syms.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - acc.join(v.widen) + acc.join(v.widen(setting2)) } // TODO: check inner classes } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 592a0f028fc9..73d5a8d102af 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -30,7 +30,7 @@ trait Indexer { self: Analyzer => def methodValue(ddef: DefDef)(implicit setting: Setting): FunctionValue = new FunctionValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { - // TODO: why implicit conversion does not work + // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx if (isChecking(ddef.symbol)) { // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. @@ -51,15 +51,21 @@ trait Indexer { self: Analyzer => } def widen(implicit setting2: Setting) = { + // TODO: implicit ambiguities + implicit val ctx: Context = setting2.ctx + val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)) + // println(setting2.env.show(setting2.showSetting)) // capture analysis - WarmValue() + val captured = Capture.analyze(ddef.rhs)(setting3) + indentedDebug(s"captured in ${ddef.symbol}: " + captured.map(_.show).mkString(", ")) + WarmValue(captured) } } def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = new LazyValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { - // TODO: why implicit conversion does not work + // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx if (isChecking(vdef.symbol)) { // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. @@ -76,8 +82,13 @@ trait Indexer { self: Analyzer => } def widen(implicit setting2: Setting) = { + // TODO: implicit ambiguities + implicit val ctx: Context = setting2.ctx + val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)) // capture analysis - WarmValue() + val captured = Capture.analyze(vdef.rhs)(setting3) + indentedDebug(s"captured in ${vdef.symbol}: " + captured.map(_.show).mkString(", ")) + WarmValue(captured) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 884173926a45..7dff8354ca99 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -362,9 +362,10 @@ case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { Res() } - def show(implicit setting: ShowSetting): String = "Warm" + def show(implicit setting: ShowSetting): String = + deps.map(_.show).mkString("Warm(", ", ", ")") - override def toString = "warm value" + override def toString = "warm value (" + deps.size + ")" } /** A function value or value of method select */ @@ -653,11 +654,14 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } } - def widen(implicit setting: Setting): OpaqueValue = - if (open) ColdValue + def widen(implicit setting: Setting): OpaqueValue = { + val o = if (open) ColdValue else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => v.widen.join(acc) } + println(o.show(setting.showSetting)) + o + } def show(implicit setting: ShowSetting): String = { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") diff --git a/tests/neg-custom-args/safe-init/inner12.scala b/tests/neg-custom-args/safe-init/inner12.scala index 7b394be18cb6..808ef086256c 100644 --- a/tests/neg-custom-args/safe-init/inner12.scala +++ b/tests/neg-custom-args/safe-init/inner12.scala @@ -4,17 +4,20 @@ object A { class Inner { val y = x - def f(n: Int) = x * n + def f(n: Int) = x * n * y } println(new Inner) // ok + + case class M(x: Int) + println(M(4)) // ok } object B { val x = 10 case class Inner(m: Int) { - def f(n: Int) = z * n + def f(n: Int) = z * n + m } println(Inner(x)) // error From 70645ceb1234e40244ee44e4e0a114682eab4876 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 25 Oct 2018 23:28:03 +0200 Subject: [PATCH 29/83] Widening of trees works --- .../tools/dotc/transform/init/Indexer.scala | 24 +++++++---- .../tools/dotc/transform/init/Setting.scala | 6 ++- .../tools/dotc/transform/init/Values.scala | 41 ++++++++++++++----- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 73d5a8d102af..f8a64ef2130f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -54,14 +54,23 @@ trait Indexer { self: Analyzer => // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)) - // println(setting2.env.show(setting2.showSetting)) - // capture analysis - val captured = Capture.analyze(ddef.rhs)(setting3) - indentedDebug(s"captured in ${ddef.symbol}: " + captured.map(_.show).mkString(", ")) - WarmValue(captured) + widenTree(ddef)(setting3) } } + def widenTree(tree: Tree)(implicit setting: Setting): OpaqueValue = { + val captured = Capture.analyze(tree) + indentedDebug(s"captured in ${tree.symbol}: " + captured.map(_.show).mkString(", ")) + + val setting2 = setting.strict + val notHot = captured.filter { tp => + val res = setting.analyzer.checkRef(tp)(setting2) + res.hasErrors || !res.value.widen.isHot + } + if (notHot.isEmpty) HotValue + else WarmValue(captured, unknownDeps = false) + } + def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = new LazyValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { @@ -85,10 +94,7 @@ trait Indexer { self: Analyzer => // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)) - // capture analysis - val captured = Capture.analyze(vdef.rhs)(setting3) - indentedDebug(s"captured in ${vdef.symbol}: " + captured.map(_.show).mkString(", ")) - WarmValue(captured) + widenTree(vdef)(setting3) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index 50355b7699b4..af635a0fb0af 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -13,8 +13,10 @@ case class Setting( pos: Position, ctx: Context, analyzer: Analyzer, - allowDynamic: Boolean = true) { - def strict: Setting = copy(allowDynamic = false) + allowDynamic: Boolean = true, + forceLazy: Boolean = true, + callParameterless: Boolean = true) { + def strict: Setting = copy(allowDynamic = false, forceLazy = false, callParameterless = false) def heap: Heap = env.heap def withPos(position: Position) = copy(pos = position) def withEnv(ienv: Env) = copy(env = ienv) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 7dff8354ca99..6b3e564dcb70 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -22,13 +22,22 @@ import collection.mutable //======================================= object Value { + def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { + def message(v: OpaqueValue) = { + s"Unsafe leak of object ${v.show(setting.showSetting)} under initialization to ${sym.show}" ++ + (v match { + case WarmValue(deps, _) => + "\nThe object captures " + deps.map(_.show).mkString("", ",", ".") + case _ => "" + }) + } paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) val wValue = value.widen if (!wValue.isHot && !tp.value.isCold || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity - return Res(effects = Vector(Generic("Unsafe leak of object under initialization to " + sym.show, pos))) + return Res(effects = Vector(Generic(message(wValue), pos))) } Res() } @@ -162,7 +171,9 @@ abstract sealed class OpaqueValue extends SingleValue { def join(that: OpaqueValue): OpaqueValue = (this, that) match { case (_, IcyValue) | (IcyValue, _) => IcyValue case (ColdValue, _) | (_, ColdValue) => ColdValue - case (WarmValue(deps1), WarmValue(deps2)) => WarmValue(deps1 ++ deps2) + case (WarmValue(deps1, unknown1), WarmValue(deps2, unknown2)) => + if (unknown1 || unknown2) WarmValue(Set.empty, unknownDeps = true) + else WarmValue(deps1 ++ deps2, unknownDeps = false) case (w: WarmValue, _) => w case (_, w: WarmValue) => w case _ => HotValue @@ -170,7 +181,11 @@ abstract sealed class OpaqueValue extends SingleValue { def meet(that: OpaqueValue): OpaqueValue = (this, that) match { case (_, HotValue) | (HotValue, _) => HotValue - case (WarmValue(deps1), WarmValue(deps2)) => WarmValue(deps1 & deps2) + case (WarmValue(deps1, unknown1), WarmValue(deps2, unknown2)) => + if (!unknown1 && !unknown2) WarmValue(deps1 & deps2, unknownDeps = false) + else if (!unknown1) this + else if (!unknown2) that + else WarmValue(Set.empty, unknownDeps = true) case (w: WarmValue, _) => w case (_, w: WarmValue) => w case (ColdValue, _) | (_, ColdValue) => ColdValue @@ -316,7 +331,7 @@ object ColdValue extends OpaqueValue { * * If `deps.isEmpty`, then the value has unknown dependencies. */ -case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { +case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = true) extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val res = Res() if (sym.is(Flags.Method)) { @@ -362,10 +377,16 @@ case class WarmValue(val deps: Set[Type] = Set.empty) extends OpaqueValue { Res() } + override def widen(implicit setting: Setting) = + if (unknownDeps || deps.nonEmpty) this else HotValue + def show(implicit setting: ShowSetting): String = - deps.map(_.show).mkString("Warm(", ", ", ")") + if (unknownDeps) "Warm(unkown)" + else deps.map(_.show).mkString("Warm(", ", ", ")") - override def toString = "warm value (" + deps.size + ")" + override def toString = + if (unknownDeps) "Warm(unkown)" + else s"Warm(${deps.size}, $unknownDeps)" } /** A function value or value of method select */ @@ -513,14 +534,14 @@ class SliceValue(val id: Int) extends SingleValue { val value = slice(sym) if (sym.is(Flags.Lazy)) { - if (value.isInstanceOf[LazyValue]) { + if (value.isInstanceOf[LazyValue] && setting.forceLazy) { val res = value(Nil, Nil) slice(sym) = res.value res } else Res(value = value) } - else if (sym.is(Flags.Method)) { + else if (sym.is(Flags.Method) && setting.callParameterless) { if (sym.info.isParameterless) { // parameter-less call value(Nil, Nil) } @@ -655,12 +676,10 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } def widen(implicit setting: Setting): OpaqueValue = { - val o = if (open) ColdValue + if (open) ColdValue else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => v.widen.join(acc) } - println(o.show(setting.showSetting)) - o } def show(implicit setting: ShowSetting): String = { From 36eaa78861d911df0271f7a87f6f9bd9ab28677e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 00:23:23 +0200 Subject: [PATCH 30/83] Only allow hot values travel through dynamic calls --- .../tools/dotc/transform/init/Values.scala | 37 ++++++++++++++----- .../safe-init/override14.scala | 10 +++++ .../safe-init/override15.scala | 11 ++++++ 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override14.scala create mode 100644 tests/neg-custom-args/safe-init/override15.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 6b3e564dcb70..ed78d6dffcf2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -23,9 +23,9 @@ import collection.mutable object Value { - def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { + def checkParams(sym: Symbol, paramInfos: List[Type], values: Int => Value, argPos: Int => Position, onlyHot: Boolean = false)(implicit setting: Setting): Res = { def message(v: OpaqueValue) = { - s"Unsafe leak of object ${v.show(setting.showSetting)} under initialization to ${sym.show}" ++ + s"Unsafe leak of object under initialization to ${sym.show}" ++ (v match { case WarmValue(deps, _) => "\nThe object captures " + deps.map(_.show).mkString("", ",", ".") @@ -36,22 +36,36 @@ object Value { val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) val wValue = value.widen - if (!wValue.isHot && !tp.value.isCold || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity + if (!wValue.isHot && !onlyHot && !tp.value.isCold || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity return Res(effects = Vector(Generic(message(wValue), pos))) } Res() } - def defaultFunctionValue(methSym: Symbol)(implicit setting: Setting): Value = { + def defaultFunctionValue(methSym: Symbol, onlyHot: Boolean = false)(implicit setting: Setting): Value = { assert(methSym.is(Flags.Method)) if (methSym.info.paramNamess.isEmpty) HotValue else new FunctionValue() { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val paramInfos = methSym.info.paramInfoss.flatten - checkParams(methSym, paramInfos, values, argPos) + checkParams(methSym, paramInfos, values, argPos, onlyHot) } - def widen(implicit setting: Setting) = HotValue + def widen(implicit setting: Setting) = ??? + } + } + + def dynamicMethodValue(methSym: Symbol, value: Value)(implicit setting: Setting): Value ={ + assert(methSym.is(Flags.Method)) + if (methSym.info.paramNamess.isEmpty) value + else new FunctionValue() { + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { + val paramInfos = methSym.info.paramInfoss.flatten + val effs = checkParams(methSym, paramInfos, values, argPos, onlyHot = true).effects + value.apply(values, argPos) ++ effs + } + + def widen(implicit setting: Setting) = ??? } } } @@ -626,18 +640,23 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } val res = Res() - - // remember dynamic calls + var checkParam = false + // dynamic calls are analysis boundary, only allow hot values if (!isStaticDispatch && !target.isEffectivelyFinal) { if (setting.allowDynamic || target.isEffectiveInit) _dynamicCalls = _dynamicCalls + target else res += Generic(s"Dynamic call to $target found", setting.pos) + + // res.value = Value.defaultFunctionValue(target, onlyHot = true) + checkParam = target.is(Flags.Method) && target.info.paramNamess.flatten.nonEmpty } val cls = target.owner.asClass if (slices.contains(cls)) { - slices(cls).select(target) ++ res.effects + val res2 = slices(cls).select(target) + if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) + res2 ++ res.effects } else { // select on unknown super diff --git a/tests/neg-custom-args/safe-init/override14.scala b/tests/neg-custom-args/safe-init/override14.scala new file mode 100644 index 000000000000..ef0ce442eefd --- /dev/null +++ b/tests/neg-custom-args/safe-init/override14.scala @@ -0,0 +1,10 @@ +abstract class A { + val x = f(this) + val y = 10 + + def f(a: A): Int +} + +class B extends A { + def f(a: A): Int = a.y +} diff --git a/tests/neg-custom-args/safe-init/override15.scala b/tests/neg-custom-args/safe-init/override15.scala new file mode 100644 index 000000000000..4dbf6a20166a --- /dev/null +++ b/tests/neg-custom-args/safe-init/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) +} From 8d97c071e3c3cd282ff2fcc7a6d4d7cf666dc5fb Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 01:16:16 +0200 Subject: [PATCH 31/83] Fix param checking of dynamic methods --- .../dotty/tools/dotc/transform/init/Checker.scala | 9 +++++---- .../dotty/tools/dotc/transform/init/Heap.scala | 4 ++-- .../dotty/tools/dotc/transform/init/Values.scala | 15 +++++++++------ tests/neg-custom-args/safe-init/override14.scala | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 517e84c81c23..d54e1b2a8f44 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -117,16 +117,17 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val values = constr.vparamss.flatten.map { param => param.tpe.widen.value } val poss = constr.vparamss.flatten.map(_.pos) val res = root.init(constr.symbol, values, poss, obj)(setting) - - val sliceValue = obj.slices(cls).asInstanceOf[SliceValue] - val slice = root.heap(sliceValue.id).asSlice + val slice = obj.slices(cls).asSlice(setting) res.effects.foreach(_.report) if (obj.open) obj.annotate(cls) // init check: try commit early - if (obj.open) initCheck(cls, obj, tmpl)(setting) + if (obj.open) { + val innerEnv = obj.slices(cls).asSlice(setting).innerEnv + initCheck(cls, obj, tmpl)(setting.withEnv(innerEnv)) + } } def coldCheck(cls: ClassSymbol, tmpl: tpd.Template, analyzer: Analyzer)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index bdecd5fee2f6..5b192f1a5945 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,9 +307,9 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - val setting2 = setting.withEnv(innerEnv.outer) + // val setting2 = setting.withEnv(innerEnv.outer) _syms.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - acc.join(v.widen(setting2)) + acc.join(v.widen) } // TODO: check inner classes } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index ed78d6dffcf2..0af093b4566c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -36,7 +36,7 @@ object Value { val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) val wValue = value.widen - if (!wValue.isHot && !onlyHot && !tp.value.isCold || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity + if (!wValue.isHot && (onlyHot || !tp.value.isCold) || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity return Res(effects = Vector(Generic(message(wValue), pos))) } Res() @@ -55,7 +55,7 @@ object Value { } } - def dynamicMethodValue(methSym: Symbol, value: Value)(implicit setting: Setting): Value ={ + def dynamicMethodValue(methSym: Symbol, value: Value)(implicit setting: Setting): Value = { assert(methSym.is(Flags.Method)) if (methSym.info.paramNamess.isEmpty) value else new FunctionValue() { @@ -119,6 +119,9 @@ sealed trait Value { */ def widen(implicit setting: Setting): OpaqueValue + def asSlice(implicit setting: Setting): SliceRep = + setting.heap(this.asInstanceOf[SliceValue].id).asSlice + def isIcy: Boolean = this == IcyValue def isCold: Boolean = this == ColdValue def isWarm: Boolean = this.isInstanceOf[WarmValue] @@ -544,7 +547,7 @@ class SliceValue(val id: Int) extends SingleValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { - val slice = setting.heap(id).asSlice + val slice = this.asSlice val value = slice(sym) if (sym.is(Flags.Lazy)) { @@ -573,20 +576,20 @@ class SliceValue(val id: Int) extends SingleValue { } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { - val slice = setting.heap(id).asSlice + val slice = this.asSlice slice(sym) = value Res() } def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { val cls = constr.owner.asClass - val slice = setting.heap(id).asSlice + val slice = this.asSlice val tmpl = slice.classInfos(cls) setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) } def widen(implicit setting: Setting): OpaqueValue = - setting.heap(id).asSlice.widen + this.asSlice.widen override def hashCode = id diff --git a/tests/neg-custom-args/safe-init/override14.scala b/tests/neg-custom-args/safe-init/override14.scala index ef0ce442eefd..451a12087103 100644 --- a/tests/neg-custom-args/safe-init/override14.scala +++ b/tests/neg-custom-args/safe-init/override14.scala @@ -1,5 +1,5 @@ abstract class A { - val x = f(this) + val x = f(this) // error val y = 10 def f(a: A): Int From 5ed4332c0432a85520aab65f55cadc787c50d8fe Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 11:27:49 +0200 Subject: [PATCH 32/83] Check overriding of class parameters --- .../tools/dotc/transform/init/Checker.scala | 15 ++++++++++----- .../tools/dotc/transform/init/Values.scala | 1 - .../tools/dotc/transform/init/package.scala | 6 ++++++ .../safe-init/override13.scala | 2 +- .../safe-init/override16.scala | 19 +++++++++++++++++++ .../neg-custom-args/safe-init/override5.scala | 2 +- 6 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override16.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index d54e1b2a8f44..c9bb3d1f4e95 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -140,19 +140,19 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (!sym.isEffectiveCold) return val root = Heap.createRootEnv - val setting: Setting = Setting(root, sym.pos, ctx, analyzer) + val setting: Setting = Setting(root, sym.pos, ctx, analyzer).strict indexOuter(cls)(setting) if (sym.isCold) root.add(cls, IcyValue) else root.add(cls, ColdValue) - val value = analyzer.methodValue(ddef)(setting.strict) - val res = value.apply(i => HotValue, i => NoPosition)(setting.strict) + val value = analyzer.methodValue(ddef)(setting) + val res = value.apply(i => HotValue, i => NoPosition)(setting) if (res.hasErrors) { ctx.warning("Calling the method during initialization causes errors", sym.pos) res.effects.foreach(_.report) } - else if (res.value != HotValue) { + else if (!res.value.widen(setting).isHot) { ctx.warning("A method called during initialization must return a fully initialized value", sym.pos) } } @@ -236,7 +236,12 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (actual.isCold) sym.annotate(defn.ColdAnnotType) else if (actual.isWarm) sym.annotate(defn.WarmAnnotType) - obj.clearDynamicCalls() + if (sym.isOverrideClassParam && !sym.isClassParam) { + setting.ctx.warning("Overriding a class parameter in class body may cause initialization problems", sym.pos) + } + else if (!sym.isHot && sym.allOverriddenSymbols.exists(_.isHot)) { + setting.ctx.warning("Overriding a fully initialized class parameter with a cold parameter may cause initialization problems", sym.pos) + } } tmpl.body.foreach { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 0af093b4566c..7435b904ef68 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -651,7 +651,6 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { else res += Generic(s"Dynamic call to $target found", setting.pos) - // res.value = Value.defaultFunctionValue(target, onlyHot = true) checkParam = target.is(Flags.Method) && target.info.paramNamess.flatten.nonEmpty } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index ef28ad3e322e..63a537de58a4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -45,6 +45,9 @@ package object init { def isWarm(implicit ctx: Context) = sym.hasAnnotation(defn.WarmAnnot) || sym.info.isWarm + def isHot(implicit ctx: Context) = + value.isHot + def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) def isEffectiveCold(implicit ctx: Context) = @@ -62,6 +65,9 @@ package object init { def isClassParam(implicit ctx: Context) = sym.is(ParamAccessor) + def isOverrideClassParam(implicit ctx: Context) = + sym.allOverriddenSymbols.exists(_.isClassParam) + def isDefinedOn(tp: Type)(implicit ctx: Context): Boolean = tp.classSymbol.isSubClass(sym.owner) diff --git a/tests/neg-custom-args/safe-init/override13.scala b/tests/neg-custom-args/safe-init/override13.scala index 09dbdc31d389..ff224cafa585 100644 --- a/tests/neg-custom-args/safe-init/override13.scala +++ b/tests/neg-custom-args/safe-init/override13.scala @@ -1,4 +1,4 @@ -class A { +abstract class A { val x = f def f: Int diff --git a/tests/neg-custom-args/safe-init/override16.scala b/tests/neg-custom-args/safe-init/override16.scala new file mode 100644 index 000000000000..2292b17c3239 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override16.scala @@ -0,0 +1,19 @@ +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: Cold[A]) extends B(new A(10)) // error + +class M(val a: Cold[A]) + +class N(override val a: Cold[A]) extends M(new A(10)) + +class X(val a: Cold[A]) + +class Y(override val a: A) extends X(new A(10)) diff --git a/tests/neg-custom-args/safe-init/override5.scala b/tests/neg-custom-args/safe-init/override5.scala index 8490b19cac68..88c8f271822b 100644 --- a/tests/neg-custom-args/safe-init/override5.scala +++ b/tests/neg-custom-args/safe-init/override5.scala @@ -29,5 +29,5 @@ trait Base { class Derived(val name: String) extends Base class Derived2 extends Derived("hello") { - override val name: String = "ok" + override val name: String = "ok" // error: be conservative here } From 320028077aa87cbac226f4da8a8c97ec794e6113 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 13:47:15 +0200 Subject: [PATCH 33/83] Don't warn when select a registered dynamic call --- compiler/src/dotty/tools/dotc/transform/init/Indexer.scala | 4 ++++ compiler/src/dotty/tools/dotc/transform/init/Values.scala | 2 +- tests/neg-custom-args/safe-init/function.scala | 2 +- tests/neg-custom-args/safe-init/function3.scala | 2 +- tests/neg-custom-args/safe-init/function5.scala | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index f8a64ef2130f..938a9eb28c0f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -65,8 +65,12 @@ trait Indexer { self: Analyzer => val setting2 = setting.strict val notHot = captured.filter { tp => val res = setting.analyzer.checkRef(tp)(setting2) + println(res.effects) res.hasErrors || !res.value.widen.isHot } + + indentedDebug(s"not hot in ${tree.symbol}: " + notHot.map(_.show).mkString(", ")) + if (notHot.isEmpty) HotValue else WarmValue(captured, unknownDeps = false) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 7435b904ef68..0777060861ee 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -648,7 +648,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (!isStaticDispatch && !target.isEffectivelyFinal) { if (setting.allowDynamic || target.isEffectiveInit) _dynamicCalls = _dynamicCalls + target - else + else if (!target.isCalledIn(target.owner.asClass) && !dynamicCalls.contains(target)) res += Generic(s"Dynamic call to $target found", setting.pos) checkParam = target.is(Flags.Method) && target.info.paramNamess.flatten.nonEmpty diff --git a/tests/neg-custom-args/safe-init/function.scala b/tests/neg-custom-args/safe-init/function.scala index 9dfbaf226b04..d2d07b0730a8 100644 --- a/tests/neg-custom-args/safe-init/function.scala +++ b/tests/neg-custom-args/safe-init/function.scala @@ -6,7 +6,7 @@ class Foo { List(5, 9).map(n => 2 + n + list.size) // error: closure is cold, but a full value expected - val list = List(1, 2, 3) + final val list = List(1, 2, 3) List(5, 9).map(n => 3 + n + list.size) // ok, `this.list` already initialized } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/function3.scala b/tests/neg-custom-args/safe-init/function3.scala index 31a957072993..0275c764e1d2 100644 --- a/tests/neg-custom-args/safe-init/function3.scala +++ b/tests/neg-custom-args/safe-init/function3.scala @@ -1,4 +1,4 @@ -class Foo { +final class Foo { def getName1(foo: Foo): String = foo.name // error getName1(this) // error diff --git a/tests/neg-custom-args/safe-init/function5.scala b/tests/neg-custom-args/safe-init/function5.scala index bc9b682255a5..d00972566014 100644 --- a/tests/neg-custom-args/safe-init/function5.scala +++ b/tests/neg-custom-args/safe-init/function5.scala @@ -1,4 +1,4 @@ -class Foo { +final class Foo { def getName1(f: () => String)(g: () => String): () => String = () => f() + g() // error val a1: () => String = () => this.name // error From 531baea7d4d0c48eb4e8cb00db406bb89080569f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 14:04:01 +0200 Subject: [PATCH 34/83] Refine dynamic call annotations --- .../tools/dotc/transform/init/Checker.scala | 6 ----- .../tools/dotc/transform/init/Indexer.scala | 2 +- .../tools/dotc/transform/init/Values.scala | 26 +++++-------------- .../tools/dotc/transform/init/package.scala | 3 +++ 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index c9bb3d1f4e95..360cc204c69b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -121,8 +121,6 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => res.effects.foreach(_.report) - if (obj.open) obj.annotate(cls) - // init check: try commit early if (obj.open) { val innerEnv = obj.slices(cls).asSlice(setting).innerEnv @@ -207,8 +205,6 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => else if (res.value.isHot && sym.isCalledIn(cls)) { // de facto @init sym.annotate(defn.InitAnnotType) } - - obj.clearDynamicCalls() } def checkLazy(vdef: tpd.ValDef)(implicit setting: Setting): Unit = { @@ -224,8 +220,6 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val value = res.value.widen(setting.strict) if (!value.isHot) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } - - obj.clearDynamicCalls() } def checkValDef(vdef: tpd.ValDef)(implicit setting: Setting): Unit = { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 938a9eb28c0f..397c8dc85ce8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -65,7 +65,7 @@ trait Indexer { self: Analyzer => val setting2 = setting.strict val notHot = captured.filter { tp => val res = setting.analyzer.checkRef(tp)(setting2) - println(res.effects) + indentedDebug(res.effects.mkString) res.hasErrors || !res.value.widen.isHot } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 0777060861ee..68fa8937e908 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -606,12 +606,6 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { private var _slices: Map[ClassSymbol, Value] = Map() def slices: Map[ClassSymbol, Value] = _slices - private var _dynamicCalls: Set[Symbol] = Set.empty - def dynamicCalls: Set[Symbol] = _dynamicCalls - def clearDynamicCalls(): Unit = { - _dynamicCalls = Set.empty - } - def add(cls: ClassSymbol, value: Value) = { if (slices.contains(cls)) { _slices = _slices.updated(cls, _slices(cls).join(value)) @@ -645,11 +639,13 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val res = Res() var checkParam = false // dynamic calls are analysis boundary, only allow hot values - if (!isStaticDispatch && !target.isEffectivelyFinal) { - if (setting.allowDynamic || target.isEffectiveInit) - _dynamicCalls = _dynamicCalls + target - else if (!target.isCalledIn(target.owner.asClass) && !dynamicCalls.contains(target)) - res += Generic(s"Dynamic call to $target found", setting.pos) + if (open && !isStaticDispatch && !target.isEffectivelyFinal) { + if (!target.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even it's called above + if (setting.allowDynamic || target.isEffectiveInit) + tp.classSymbol.addAnnotation(Annotation.Call(sym)) + else + res += Generic(s"Dynamic call to $target found", setting.pos) + } checkParam = target.is(Flags.Method) && target.info.paramNamess.flatten.nonEmpty } @@ -707,12 +703,4 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") "Object {\n" + setting.indent(body) + "\n}" } - - def annotate(cls: ClassSymbol)(implicit ctx: Context) = { - dynamicCalls.foreach { sym => - debug(s"$sym used during initialization of $cls") - cls.addAnnotation(Annotation.Call(sym)) - } - _dynamicCalls = Set.empty - } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 63a537de58a4..70839be9f9a0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -63,6 +63,9 @@ package object init { def isCalledAbove(from: ClassSymbol)(implicit ctx: Context) = from.baseClasses.tail.exists(cls => sym.isCalledIn(cls)) + def isCalledOrAbove(from: ClassSymbol)(implicit ctx: Context) = + from.baseClasses.exists(cls => sym.isCalledIn(cls)) + def isClassParam(implicit ctx: Context) = sym.is(ParamAccessor) def isOverrideClassParam(implicit ctx: Context) = From 2bd5f82edbfd93dc3e620f4dce56117c3a16ad30 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 15:17:01 +0200 Subject: [PATCH 35/83] Check inner class annotation in parents --- .../tools/dotc/transform/init/Capture.scala | 15 ++++++------- .../tools/dotc/transform/init/Checker.scala | 19 +++++++++++++++++ .../tools/dotc/transform/init/Indexer.scala | 6 +++--- .../tools/dotc/transform/init/Values.scala | 21 +++++++++++-------- tests/neg-custom-args/safe-init/inner1.scala | 2 +- tests/neg-custom-args/safe-init/inner11.scala | 4 ++-- tests/neg-custom-args/safe-init/inner6.scala | 2 +- 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala index f3ced2b559b4..99a0d4b4750e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala @@ -25,7 +25,7 @@ import collection.mutable object Capture { private class CaptureTraverser(setting: Setting) extends TreeTraverser { - val captured: mutable.Set[Type] = mutable.Set.empty + val captured: mutable.Map[Type, List[Tree]] = mutable.Map.empty def trace[T](msg: => String)(body: => T) = { println(msg) @@ -48,18 +48,19 @@ object Capture { case _ => false }) - def check(tp: Type)(implicit ctx: Context) = if (free(tp)) captured += tp + def check(tree: Tree)(implicit ctx: Context) = + if (free(tree.tpe)) captured(tree.tpe) = tree :: captured.getOrElseUpdate(tree.tpe, Nil) def traverse(tree: Tree)(implicit ctx: Context) = try { //debug tree match { case tree: Ident => - check(tree.tpe) + check(tree) case Trees.Select(ths: This, _) => - check(tree.tpe) + check(tree) case tree: Select => traverse(tree.qualifier) case tree: This => - check(tree.tpe) + check(tree) case tree if tree.isType =>// ignore all type trees case _ => traverseChildren(tree) @@ -71,9 +72,9 @@ object Capture { } } - def analyze(tree: Tree)(implicit setting: Setting): Set[Type] = { + def analyze(tree: Tree)(implicit setting: Setting): Map[Type, List[Tree]] = { val cap = new CaptureTraverser(setting) cap.traverse(tree) - cap.captured.toSet + cap.captured.toMap } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 360cc204c69b..1334c5a7a477 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -238,6 +238,23 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => } } + def checkClassDef(cdef: tpd.TypeDef)(implicit setting: Setting): Unit = { + val sym = cdef.symbol + if (sym.isInit) { + val value = setting.analyzer.widenTree(cdef) + + val captured = Capture.analyze(cdef) + val setting2 = setting.strict + val notHot = captured.keys.filter { tp => + val res = setting.analyzer.checkRef(tp)(setting2) + res.hasErrors || !res.value.widen.isHot + } + + for(key <- notHot; tree <- captured(key)) + setting.ctx.warning(s"The init $sym captures " + tree.show + ".\nTry to make captured fields or methods private or final.", tree.pos) + } + } + tmpl.body.foreach { case ddef: DefDef if !ddef.symbol.hasAnnotation(defn.UncheckedAnnot) => checkMethod(ddef)(setting.withPos(ddef.symbol.pos)) @@ -245,6 +262,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => checkLazy(vdef)(setting.withPos(vdef.symbol.pos)) case vdef: ValDef => checkValDef(vdef)(setting.withPos(vdef.symbol.pos)) + case cdef: TypeDef if cdef.isClassDef => + checkClassDef(cdef)(setting.withPos(cdef.symbol.pos)) case _ => } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 397c8dc85ce8..88bdd582c6fe 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -60,10 +60,10 @@ trait Indexer { self: Analyzer => def widenTree(tree: Tree)(implicit setting: Setting): OpaqueValue = { val captured = Capture.analyze(tree) - indentedDebug(s"captured in ${tree.symbol}: " + captured.map(_.show).mkString(", ")) + indentedDebug(s"captured in ${tree.symbol}: " + captured.keys.map(_.show).mkString(", ")) val setting2 = setting.strict - val notHot = captured.filter { tp => + val notHot = captured.keys.filter { tp => val res = setting.analyzer.checkRef(tp)(setting2) indentedDebug(res.effects.mkString) res.hasErrors || !res.value.widen.isHot @@ -72,7 +72,7 @@ trait Indexer { self: Analyzer => indentedDebug(s"not hot in ${tree.symbol}: " + notHot.map(_.show).mkString(", ")) if (notHot.isEmpty) HotValue - else WarmValue(captured, unknownDeps = false) + else WarmValue(notHot.toSet, unknownDeps = false) } def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 68fa8937e908..6999471e54fc 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -42,13 +42,13 @@ object Value { Res() } - def defaultFunctionValue(methSym: Symbol, onlyHot: Boolean = false)(implicit setting: Setting): Value = { + def defaultFunctionValue(methSym: Symbol)(implicit setting: Setting): Value = { assert(methSym.is(Flags.Method)) if (methSym.info.paramNamess.isEmpty) HotValue else new FunctionValue() { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = { val paramInfos = methSym.info.paramInfoss.flatten - checkParams(methSym, paramInfos, values, argPos, onlyHot) + checkParams(methSym, paramInfos, values, argPos, onlyHot = true) } def widen(implicit setting: Setting) = ??? @@ -383,15 +383,18 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru if (res.hasErrors) return res val cls = constr.owner.asClass - if (!cls.isCold && !cls.isWarm) { + if (cls.isInit) { + if (values.exists(!_.widen.isHot)) obj.add(cls, WarmValue()) + Res() + } + else if (cls.isCold || cls.isWarm) { + obj.add(cls, WarmValue()) + Res() + } + else { res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) - res.value = HotValue - return res + res } - - obj.add(cls, WarmValue()) - - Res() } override def widen(implicit setting: Setting) = diff --git a/tests/neg-custom-args/safe-init/inner1.scala b/tests/neg-custom-args/safe-init/inner1.scala index ff9470c6830f..e1db2d2fc31b 100644 --- a/tests/neg-custom-args/safe-init/inner1.scala +++ b/tests/neg-custom-args/safe-init/inner1.scala @@ -1,5 +1,5 @@ class Foo { - val bar = new Bar(this) // error + val bar = new Bar(this) new bar.Inner // error new this.Inner // error, as Inner access `this.list` diff --git a/tests/neg-custom-args/safe-init/inner11.scala b/tests/neg-custom-args/safe-init/inner11.scala index 979f66530690..0cc8fedc7363 100644 --- a/tests/neg-custom-args/safe-init/inner11.scala +++ b/tests/neg-custom-args/safe-init/inner11.scala @@ -4,7 +4,7 @@ object NameKinds { abstract class NameKind(val tag: Int) { self => type ThisInfo <: Info - @scala.annotation.init + @scala.annotation.cold class Info extends NameInfo { this: ThisInfo => def kind = self } @@ -17,7 +17,7 @@ object NameKinds { } } -object NameKinds { +object NameKinds2 { abstract class NameInfo abstract class NameKind(val tag: Int) { self => diff --git a/tests/neg-custom-args/safe-init/inner6.scala b/tests/neg-custom-args/safe-init/inner6.scala index 2912a205ea1b..75f702acc313 100644 --- a/tests/neg-custom-args/safe-init/inner6.scala +++ b/tests/neg-custom-args/safe-init/inner6.scala @@ -8,7 +8,7 @@ class Parent { val len = foo } - val list = List(3, 5, 6) + private val list = List(3, 5, 6) def foo: Int = 5 } From 59a8324d061a5988c6648abfc887578c0a3d897a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 15:59:14 +0200 Subject: [PATCH 36/83] Don't annotate method call on selection error --- .../tools/dotc/transform/init/Values.scala | 62 +++++++++++-------- tests/neg-custom-args/safe-init/example.scala | 4 +- tests/neg-custom-args/safe-init/parent6.scala | 1 + 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 6999471e54fc..f1454479b59b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -629,41 +629,49 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { - val target = if (isStaticDispatch) sym else resolve(sym) - - // select on self type - if (!target.exists) { - if (sym.owner.is(Flags.Trait)) - return ColdValue.select(sym) - else - return WarmValue().select(sym) - } - val res = Res() var checkParam = false + var addAnnotation = false // dynamic calls are analysis boundary, only allow hot values - if (open && !isStaticDispatch && !target.isEffectivelyFinal) { - if (!target.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even it's called above - if (setting.allowDynamic || target.isEffectiveInit) - tp.classSymbol.addAnnotation(Annotation.Call(sym)) + if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { + if (!sym.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even it's called above + if (setting.allowDynamic || sym.isEffectiveInit) + addAnnotation = true else - res += Generic(s"Dynamic call to $target found", setting.pos) + res += Generic(s"Dynamic call to $sym found", setting.pos) } - checkParam = target.is(Flags.Method) && target.info.paramNamess.flatten.nonEmpty + checkParam = sym.is(Flags.Method) && sym.info.paramNamess.flatten.nonEmpty } - val cls = target.owner.asClass - if (slices.contains(cls)) { - val res2 = slices(cls).select(target) - if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) - res2 ++ res.effects - } - else { - // select on unknown super - assert(target.isDefinedOn(tp)) - WarmValue().select(target) ++ res.effects - } + val target = if (isStaticDispatch) sym else resolve(sym) + + // select on self type + val ret = + if (!target.exists) { + if (sym.owner.is(Flags.Trait)) + ColdValue.select(sym) + else + WarmValue().select(sym) + } + else { + val cls = target.owner.asClass + if (slices.contains(cls)) { + val res2 = slices(cls).select(target) + if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) + res2 ++ res.effects + } + else { + // select on unknown super + assert(target.isDefinedOn(tp)) + WarmValue().select(target) ++ res.effects + } + } + + if (!ret.hasErrors && addAnnotation) + tp.classSymbol.addAnnotation(Annotation.Call(sym)) + + ret } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { diff --git a/tests/neg-custom-args/safe-init/example.scala b/tests/neg-custom-args/safe-init/example.scala index e48c4ca1c09e..9262dcea00fc 100644 --- a/tests/neg-custom-args/safe-init/example.scala +++ b/tests/neg-custom-args/safe-init/example.scala @@ -15,7 +15,9 @@ class Parent(x: Int) { f(20) // error: list not initialized - val list = List(1, 2, 3) + final val list = List(1, 2, 3) + + bar.result // ok: list initialized if (x > 5) { name = "big" diff --git a/tests/neg-custom-args/safe-init/parent6.scala b/tests/neg-custom-args/safe-init/parent6.scala index a29455eb3f85..ab82ffc04a77 100644 --- a/tests/neg-custom-args/safe-init/parent6.scala +++ b/tests/neg-custom-args/safe-init/parent6.scala @@ -1,4 +1,5 @@ trait Foo { + @scala.annotation.init class A class B { From 80248051ed3b7ca0862d4ad99b67a3fc8ecfbf5a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 16:44:29 +0200 Subject: [PATCH 37/83] Try widen conditional value --- .../tools/dotc/transform/init/Values.scala | 30 ++++++++++++++++--- .../neg-custom-args/safe-init/function4.scala | 4 +-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index f1454479b59b..fd2577b84f5a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -119,6 +119,9 @@ sealed trait Value { */ def widen(implicit setting: Setting): OpaqueValue + def tryWiden(implicit setting: Setting): Value = + if (this.widen.isHot) HotValue else this + def asSlice(implicit setting: Setting): SliceRep = setting.heap(this.asInstanceOf[SliceValue].id).asSlice @@ -218,8 +221,8 @@ object HotValue extends OpaqueValue { else Res() def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = - if (value.widen != HotValue) - Res(effects = Vector(Generic("Cannot assign an object under initialization to a full object", setting.pos))) + if (!value.widen.isHot) + Res(effects = Vector(Generic("Cannot assign an object under initialization to a fully initialized object", setting.pos))) else Res() def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { @@ -229,7 +232,12 @@ object HotValue extends OpaqueValue { if (res.hasErrors) return res val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) - if (args.exists(!_.widen.isHot)) obj.add(cls, WarmValue()) + val argsV = args.foldLeft(HotValue: OpaqueValue) { (acc, v) => + acc.join(v.widen) + } + + if (cls == obj.tp.classSymbol && !obj.open) obj.add(cls, argsV.meet(WarmValue())) + else if (!argsV.isHot) obj.add(cls, WarmValue()) Res() } @@ -350,6 +358,8 @@ object ColdValue extends OpaqueValue { */ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = true) extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { + if (widen.isHot) return HotValue.select(sym, isStaticDispatch) + val res = Res() if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) @@ -398,7 +408,15 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru } override def widen(implicit setting: Setting) = - if (unknownDeps || deps.nonEmpty) this else HotValue + if (unknownDeps) this else { + val setting2 = setting.strict + val notHot = deps.filter { tp => + val res = setting.analyzer.checkRef(tp)(setting2) + res.hasErrors || !res.value.widen.isHot + } + if (notHot.isEmpty) HotValue + else WarmValue(notHot.toSet, unknownDeps = false) + } def show(implicit setting: ShowSetting): String = if (unknownDeps) "Warm(unkown)" @@ -643,6 +661,10 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { checkParam = sym.is(Flags.Method) && sym.info.paramNamess.flatten.nonEmpty } + else { + // try commit early + // if (widen.isHot) return HotValue.select(sym, isStaticDispatch) + } val target = if (isStaticDispatch) sym else resolve(sym) diff --git a/tests/neg-custom-args/safe-init/function4.scala b/tests/neg-custom-args/safe-init/function4.scala index a29edcda47cf..56bc0a6142ba 100644 --- a/tests/neg-custom-args/safe-init/function4.scala +++ b/tests/neg-custom-args/safe-init/function4.scala @@ -1,5 +1,5 @@ -class Foo { - def getSize(f: () => String): () => Int = () => f().size // error +final class Foo { + private def getSize(f: () => String): () => Int = () => f().size // error val f1 = getSize(() => this.name) // error val f2 = getSize(() => "Jack") From 75f7f2560c78943053e9a9c87749646aa2fdd0cf Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 26 Oct 2018 17:07:21 +0200 Subject: [PATCH 38/83] Refactor widening of SliceRep --- .../src/dotty/tools/dotc/transform/init/Heap.scala | 13 +++++++++---- .../dotty/tools/dotc/transform/init/Values.scala | 4 ---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 5b192f1a5945..7f0755e20d37 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -307,10 +307,15 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo } def widen(implicit setting: Setting): OpaqueValue = { - // val setting2 = setting.withEnv(innerEnv.outer) - _syms.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - acc.join(v.widen) - } + def unInitField(sym: Symbol, value: Value): Boolean = + sym.isField && value == NoValue + + def capturingDef(sym: Symbol, value: Value): Boolean = + (sym.is(Flags.Method) || sym.is(Flags.Lazy)) && !value.widen.isHot + + if (symbols.exists { case (sym, v) => unInitField(sym, v) }) ColdValue + else if (symbols.exists { case (sym, v) => capturingDef(sym, v) }) ColdValue + else HotValue // TODO: check inner classes } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index fd2577b84f5a..94c2e88a891e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -661,10 +661,6 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { checkParam = sym.is(Flags.Method) && sym.info.paramNamess.flatten.nonEmpty } - else { - // try commit early - // if (widen.isHot) return HotValue.select(sym, isStaticDispatch) - } val target = if (isStaticDispatch) sym else resolve(sym) From 67effb8b462ebb3c689fff6a9ce1501944425565 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 29 Oct 2018 16:19:35 +0100 Subject: [PATCH 39/83] Treat self as ColdValue --- .../tools/dotc/transform/init/Values.scala | 32 ++++++++----------- .../safe-init/override17.scala | 7 ++++ 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override17.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 94c2e88a891e..9d29736dfedc 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -647,9 +647,15 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { + val target = if (isStaticDispatch) sym else resolve(sym) + + // select on self type + if (!target.exists) return ColdValue.select(sym) + val res = Res() var checkParam = false var addAnnotation = false + // dynamic calls are analysis boundary, only allow hot values if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { if (!sym.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even it's called above @@ -662,28 +668,18 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { checkParam = sym.is(Flags.Method) && sym.info.paramNamess.flatten.nonEmpty } - val target = if (isStaticDispatch) sym else resolve(sym) + val cls = target.owner.asClass - // select on self type val ret = - if (!target.exists) { - if (sym.owner.is(Flags.Trait)) - ColdValue.select(sym) - else - WarmValue().select(sym) + if (slices.contains(cls)) { + val res2 = slices(cls).select(target) + if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) + res2 ++ res.effects } else { - val cls = target.owner.asClass - if (slices.contains(cls)) { - val res2 = slices(cls).select(target) - if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) - res2 ++ res.effects - } - else { - // select on unknown super - assert(target.isDefinedOn(tp)) - WarmValue().select(target) ++ res.effects - } + // select on unknown super + assert(target.isDefinedOn(tp)) + WarmValue().select(target) ++ res.effects } if (!ret.hasErrors && addAnnotation) diff --git a/tests/neg-custom-args/safe-init/override17.scala b/tests/neg-custom-args/safe-init/override17.scala new file mode 100644 index 000000000000..341a8744562a --- /dev/null +++ b/tests/neg-custom-args/safe-init/override17.scala @@ -0,0 +1,7 @@ +class A { self : B => + val y = x +} + +class B { + val x = 10 +} \ No newline at end of file From f0ea2b5e2f9b9afcec4e0f4816aab9560b5f03b4 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 29 Oct 2018 16:49:17 +0100 Subject: [PATCH 40/83] Support calling abstract methods from parent --- .../tools/dotc/transform/init/Values.scala | 35 ++++++++----------- .../safe-init/override18.scala | 23 ++++++++++++ .../safe-init/override19.scala | 10 ++++++ 3 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/override18.scala create mode 100644 tests/neg-custom-args/safe-init/override19.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 9d29736dfedc..2091ddea7b2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -653,14 +653,14 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (!target.exists) return ColdValue.select(sym) val res = Res() + var checkParam = false - var addAnnotation = false // dynamic calls are analysis boundary, only allow hot values if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { - if (!sym.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even it's called above - if (setting.allowDynamic || sym.isEffectiveInit) - addAnnotation = true + if (!sym.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even though it's called above + if (setting.allowDynamic || sym.isEffectiveInit || target.is(Flags.Deferred)) + tp.classSymbol.addAnnotation(Annotation.Call(sym)) else res += Generic(s"Dynamic call to $sym found", setting.pos) } @@ -670,22 +670,17 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val cls = target.owner.asClass - val ret = - if (slices.contains(cls)) { - val res2 = slices(cls).select(target) - if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) - res2 ++ res.effects - } - else { - // select on unknown super - assert(target.isDefinedOn(tp)) - WarmValue().select(target) ++ res.effects - } - - if (!ret.hasErrors && addAnnotation) - tp.classSymbol.addAnnotation(Annotation.Call(sym)) - - ret + if (slices.contains(cls)) { + val res2 = slices(cls).select(target) + if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) + res2 ++ res.effects + } + else if(!target.isCalledOrAbove(tp.classSymbol.asClass)) { + // select on super, which is external + assert(target.isDefinedOn(tp)) + WarmValue().select(target) ++ res.effects + } + else res } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { diff --git a/tests/neg-custom-args/safe-init/override18.scala b/tests/neg-custom-args/safe-init/override18.scala new file mode 100644 index 000000000000..330930c7f143 --- /dev/null +++ b/tests/neg-custom-args/safe-init/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 // error + val y = x + + def f: Int = y // error +} + +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/neg-custom-args/safe-init/override19.scala b/tests/neg-custom-args/safe-init/override19.scala new file mode 100644 index 000000000000..6ce48e308ad0 --- /dev/null +++ b/tests/neg-custom-args/safe-init/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 + def productArity: Int = y // error // error +} \ No newline at end of file From 0d652a90dc9e90aca4d91383ca4b772370487717 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 29 Oct 2018 17:15:19 +0100 Subject: [PATCH 41/83] Use @icy for icy methods --- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../tools/dotc/transform/init/Checker.scala | 2 +- .../tools/dotc/transform/init/Values.scala | 56 +++++++++++-------- .../tools/dotc/transform/init/package.scala | 7 ++- library/src/scala/annotation/icy.scala | 14 +++++ .../safe-init/override20.scala | 23 ++++++++ 6 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 library/src/scala/annotation/icy.scala create mode 100644 tests/neg-custom-args/safe-init/override20.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index fad3f2f41385..e96ed27e7330 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -806,6 +806,8 @@ class Definitions { def SetterMetaAnnot(implicit ctx: Context): ClassSymbol = SetterMetaAnnotType.symbol.asClass lazy val ShowAsInfixAnotType: TypeRef = ctx.requiredClassRef("scala.annotation.showAsInfix") def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass + lazy val IcyAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.icy") + def IcyAnnot(implicit ctx: Context): ClassSymbol = IcyAnnotType.symbol.asClass lazy val ColdAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.cold") def ColdAnnot(implicit ctx: Context): ClassSymbol = ColdAnnotType.symbol.asClass lazy val WarmAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.warm") diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 1334c5a7a477..0976a0d99604 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -140,7 +140,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val root = Heap.createRootEnv val setting: Setting = Setting(root, sym.pos, ctx, analyzer).strict indexOuter(cls)(setting) - if (sym.isCold) root.add(cls, IcyValue) + if (sym.isIcy) root.add(cls, IcyValue) else root.add(cls, ColdValue) val value = analyzer.methodValue(ddef)(setting) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 2091ddea7b2f..10dd7027f207 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -247,7 +247,7 @@ object HotValue extends OpaqueValue { override def toString = "hot value" } -/** An icy value, where class/trait params are not yet initialized +/** An icy value, where trait params are not yet initialized * * abstract class A { * def f: Int @@ -266,21 +266,21 @@ object IcyValue extends OpaqueValue { val res = Res(value = HotValue) if (sym.is(Flags.Method)) { - if (!sym.isCold && !sym.name.is(DefaultGetterName)) - res += Generic(s"The $sym should be marked as `@cold` in order to be called", setting.pos) + if (!sym.isIcy && !sym.name.is(DefaultGetterName)) + res += Generic(s"The $sym should be marked as `@icy` in order to be called", setting.pos) res.value = Value.defaultFunctionValue(sym) } else if (sym.is(Flags.Lazy)) { - if (!sym.isCold) - res += Generic(s"The lazy field $sym should be marked as `@cold` in order to be accessed", setting.pos) + if (!sym.isIcy) + res += Generic(s"The lazy field $sym should be marked as `@icy` in order to be accessed", setting.pos) } else if (sym.isClass) { - if (!sym.isCold) - res += Generic(s"The nested $sym should be marked as `@cold` in order to be instantiated", setting.pos) + if (!sym.isIcy) + res += Generic(s"The nested $sym should be marked as `@icy` in order to be instantiated", setting.pos) } else { // field select - res += Generic(s"The $sym may not be initialized", setting.pos) + res += Generic(s"$sym may not be initialized", setting.pos) } res @@ -650,17 +650,21 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val target = if (isStaticDispatch) sym else resolve(sym) // select on self type - if (!target.exists) return ColdValue.select(sym) + if (!target.exists) { + if (sym.owner.is(Flags.Trait)) return IcyValue.select(sym) + else return ColdValue.select(sym) + } val res = Res() var checkParam = false - + var addAnnotation = false // dynamic calls are analysis boundary, only allow hot values if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { - if (!sym.isCalledIn(tp.classSymbol.asClass)) { // annotation on current class even though it's called above + if (!sym.isCalledIn(tp.classSymbol.asClass)) { // avoid duplicate annotatino + // annotation on current class even though it's called above if (setting.allowDynamic || sym.isEffectiveInit || target.is(Flags.Deferred)) - tp.classSymbol.addAnnotation(Annotation.Call(sym)) + addAnnotation = true else res += Generic(s"Dynamic call to $sym found", setting.pos) } @@ -670,17 +674,23 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val cls = target.owner.asClass - if (slices.contains(cls)) { - val res2 = slices(cls).select(target) - if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) - res2 ++ res.effects - } - else if(!target.isCalledOrAbove(tp.classSymbol.asClass)) { - // select on super, which is external - assert(target.isDefinedOn(tp)) - WarmValue().select(target) ++ res.effects - } - else res + val res = + if (slices.contains(cls)) { + val res2 = slices(cls).select(target) + if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) + res2 ++ res.effects + } + else if(!target.isCalledOrAbove(tp.classSymbol.asClass)) { + // select on super, which is external + assert(target.isDefinedOn(tp)) + WarmValue().select(target) ++ res.effects + } + else res + + if (!res.hasErrors && addAnnotation) + tp.classSymbol.addAnnotation(Annotation.Call(sym)) + + res } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { diff --git a/compiler/src/dotty/tools/dotc/transform/init/package.scala b/compiler/src/dotty/tools/dotc/transform/init/package.scala index 70839be9f9a0..c2f5c8a5ca3e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/package.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/package.scala @@ -23,6 +23,8 @@ import collection.mutable package object init { implicit class TypeOps(val tp: Type) extends AnyVal { + def isIcy(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.IcyAnnot) + def isCold(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.ColdAnnot) def isWarm(implicit ctx: Context) = tp.dealiasKeepAnnots.hasAnnotation(defn.WarmAnnot) @@ -39,6 +41,9 @@ package object init { } implicit class SymOps(val sym: Symbol) extends AnyVal { + def isIcy(implicit ctx: Context) = + sym.hasAnnotation(defn.IcyAnnot) || sym.info.isIcy + def isCold(implicit ctx: Context) = sym.hasAnnotation(defn.ColdAnnot) || sym.info.isCold @@ -51,7 +56,7 @@ package object init { def isInit(implicit ctx: Context) = sym.hasAnnotation(defn.InitAnnot) def isEffectiveCold(implicit ctx: Context) = - sym.isCold || sym.isCalledAbove(sym.owner.asClass) + sym.isIcy || sym.isCold || sym.isCalledAbove(sym.owner.asClass) def isEffectiveInit(implicit ctx: Context) = !sym.isEffectiveCold && diff --git a/library/src/scala/annotation/icy.scala b/library/src/scala/annotation/icy.scala new file mode 100644 index 000000000000..b61338b91ee8 --- /dev/null +++ b/library/src/scala/annotation/icy.scala @@ -0,0 +1,14 @@ +package scala.annotation + +/** An annotation to indicate that a object may be + * under initialization, i.e. some fields may not + * be assigned yet, and parameters of traits may + * not be assigned yet. + * + * When used on methods, it means `this` and `super` + * are icy. + * + * When used on constructors, it means the immediate + * outer is icy. + */ +class icy extends StaticAnnotation diff --git a/tests/neg-custom-args/safe-init/override20.scala b/tests/neg-custom-args/safe-init/override20.scala new file mode 100644 index 000000000000..d7c3c1e3c372 --- /dev/null +++ b/tests/neg-custom-args/safe-init/override20.scala @@ -0,0 +1,23 @@ +class A { self : B => + val y = f // error +} + +trait B(x: Int) { + def f: Int = x +} + +class C extends A with B(20) + +////////////// + + +class X { self : Y => + val y = f +} + +trait Y(x: Int) { + @scala.annotation.icy + def f: Int = x // error +} + +class Z extends A with B(20) From b0239deb17382c25ac368a098e157d7122d19182 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 29 Oct 2018 17:30:02 +0100 Subject: [PATCH 42/83] WIP - fix infinite loop in widening --- .../tools/dotc/transform/init/Analyzer.scala | 4 ++-- .../tools/dotc/transform/init/Capture.scala | 6 ++++-- .../tools/dotc/transform/init/Heap.scala | 2 +- .../tools/dotc/transform/init/Indexer.scala | 19 +++++++++++++------ .../tools/dotc/transform/init/Setting.scala | 17 ++++++++++++++++- .../tools/dotc/transform/init/Values.scala | 17 ++++++++--------- tests/neg-custom-args/safe-init/inner13.scala | 15 +++++++++++++++ .../safe-init/override17.scala | 2 +- .../safe-init/override20.scala | 2 +- .../neg-custom-args/safe-init/override7.scala | 2 +- 10 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/inner13.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 0f2f5b983aa0..7af45aead5b7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -61,7 +61,7 @@ class Analyzer extends Indexer { analyzer => def checkSelect(tree: Select)(implicit setting: Setting): Res = { val prefixRes = apply(tree.qualifier) - val res = prefixRes.value.select(tree.symbol) + val res = prefixRes.value.tryWiden.select(tree.symbol) res.effects = prefixRes.effects ++ res.effects res } @@ -74,7 +74,7 @@ class Analyzer extends Indexer { analyzer => setting.env.select(tp.symbol) case tp @ TermRef(prefix, _) => val res = checkRef(prefix) - res.value.select(tp.symbol) + res.value.tryWiden.select(tp.symbol) case tp @ ThisType(tref) => val cls = tref.symbol if (cls.is(Package)) Res() // Dotty represents package path by ThisType diff --git a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala index 99a0d4b4750e..bf7696f093b5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala @@ -40,8 +40,8 @@ object Capture { free(ThisType.raw(tp.symbol.moduleClass.typeRef)) case tp @ TermRef(NoPrefix, _) => setting.env.contains(tp.symbol) - case tp @ TermRef(prefix, _) => - free(prefix) + case tp: NamedType => + free(tp.prefix) case tp @ ThisType(tref) => val cls = tref.symbol !cls.is(Package) && setting.env.contains(cls) @@ -61,6 +61,8 @@ object Capture { traverse(tree.qualifier) case tree: This => check(tree) + case tree: New => + check(tree) case tree if tree.isType =>// ignore all type trees case _ => traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 7f0755e20d37..384e26c76be1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -238,7 +238,7 @@ class Env(outerId: Int) extends HeapEntry { } } -/** A container holds all information about fields of an class slice of an object +/** A container holds all information about fields of a class slice of an object */ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Cloneable { override def clone: SliceRep = super.clone.asInstanceOf[SliceRep] diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 88bdd582c6fe..35fb317ae33f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -53,8 +53,9 @@ trait Indexer { self: Analyzer => def widen(implicit setting2: Setting) = { // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx - val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)) - widenTree(ddef)(setting3) + val env = setting2.heap(setting.env.id).asEnv + val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)).withEnv(env) + setting3.widen(ddef.symbol.typeRef) { widenTree(ddef)(setting3) } } } @@ -64,9 +65,14 @@ trait Indexer { self: Analyzer => val setting2 = setting.strict val notHot = captured.keys.filter { tp => - val res = setting.analyzer.checkRef(tp)(setting2) + val res = tp match { + case tp: TypeRef => // TODO: check class body + checkRef(tp.prefix)(setting2) + case _ => + checkRef(tp)(setting2) + } indentedDebug(res.effects.mkString) - res.hasErrors || !res.value.widen.isHot + res.hasErrors || !(setting2.widen(tp) { res.value.widen }).isHot } indentedDebug(s"not hot in ${tree.symbol}: " + notHot.map(_.show).mkString(", ")) @@ -97,8 +103,9 @@ trait Indexer { self: Analyzer => def widen(implicit setting2: Setting) = { // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx - val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)) - widenTree(vdef)(setting3) + val env = setting2.heap(setting.env.id).asEnv + val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)).withEnv(env) + setting3.widen(vdef.symbol.typeRef) { widenTree(vdef)(setting3) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index af635a0fb0af..316deff256dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -3,10 +3,13 @@ package transform package init import core._ +import Symbols._ +import Types._ import Contexts.Context import util.Positions._ import config.Printers.init.{ println => debug } +import collection.mutable case class Setting( env: Env, @@ -15,7 +18,8 @@ case class Setting( analyzer: Analyzer, allowDynamic: Boolean = true, forceLazy: Boolean = true, - callParameterless: Boolean = true) { + callParameterless: Boolean = true, + wideningValues: mutable.Set[Type] = mutable.Set.empty) { def strict: Setting = copy(allowDynamic = false, forceLazy = false, callParameterless = false) def heap: Heap = env.heap def withPos(position: Position) = copy(pos = position) @@ -28,5 +32,16 @@ case class Setting( copy(env = env2.asEnv) } + def isWidening: Boolean = wideningValues.nonEmpty + + def widen(tp: Type)(value: => OpaqueValue) = + if (wideningValues.contains(tp)) HotValue + else { + wideningValues += tp + val res = value + wideningValues -= tp + res + } + def showSetting = ShowSetting(heap, ctx) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 10dd7027f207..b2c91823f2c3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -120,7 +120,7 @@ sealed trait Value { def widen(implicit setting: Setting): OpaqueValue def tryWiden(implicit setting: Setting): Value = - if (this.widen.isHot) HotValue else this + if (!setting.isWidening && this.widen.isHot) HotValue else this def asSlice(implicit setting: Setting): SliceRep = setting.heap(this.asInstanceOf[SliceValue].id).asSlice @@ -358,8 +358,6 @@ object ColdValue extends OpaqueValue { */ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = true) extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { - if (widen.isHot) return HotValue.select(sym, isStaticDispatch) - val res = Res() if (sym.is(Flags.Method)) { if (!sym.isCold && !sym.isEffectiveInit && !sym.name.is(DefaultGetterName)) @@ -650,9 +648,10 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val target = if (isStaticDispatch) sym else resolve(sym) // select on self type - if (!target.exists) { - if (sym.owner.is(Flags.Trait)) return IcyValue.select(sym) - else return ColdValue.select(sym) + if (!target.exists) return { + if (sym.owner.is(Flags.Trait)) IcyValue.select(sym) + else if (tp.classSymbol.is(Flags.Trait)) WarmValue().select(sym) // classes are always init before traits + else ColdValue.select(sym) } val res = Res() @@ -674,7 +673,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val cls = target.owner.asClass - val res = + val ret = if (slices.contains(cls)) { val res2 = slices(cls).select(target) if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) @@ -687,10 +686,10 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } else res - if (!res.hasErrors && addAnnotation) + if (!ret.hasErrors && addAnnotation) tp.classSymbol.addAnnotation(Annotation.Call(sym)) - res + ret } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { diff --git a/tests/neg-custom-args/safe-init/inner13.scala b/tests/neg-custom-args/safe-init/inner13.scala new file mode 100644 index 000000000000..9b0f5427758f --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner13.scala @@ -0,0 +1,15 @@ +final class A { + val x = 10 + + final class Inner { + def f(n: Int) = println(this) + val y = (n: Int) => f(20) + + println(this) + } + + new Inner // check for recursion + + val y = z // error + val z = 10 +} \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/override17.scala b/tests/neg-custom-args/safe-init/override17.scala index 341a8744562a..39e253164d69 100644 --- a/tests/neg-custom-args/safe-init/override17.scala +++ b/tests/neg-custom-args/safe-init/override17.scala @@ -1,5 +1,5 @@ class A { self : B => - val y = x + val y = x // error } class B { diff --git a/tests/neg-custom-args/safe-init/override20.scala b/tests/neg-custom-args/safe-init/override20.scala index d7c3c1e3c372..4e21cde64f71 100644 --- a/tests/neg-custom-args/safe-init/override20.scala +++ b/tests/neg-custom-args/safe-init/override20.scala @@ -17,7 +17,7 @@ class X { self : Y => trait Y(x: Int) { @scala.annotation.icy - def f: Int = x // error + def f: Int = x // error // error } class Z extends A with B(20) diff --git a/tests/neg-custom-args/safe-init/override7.scala b/tests/neg-custom-args/safe-init/override7.scala index 3d1504cf6170..ff77fef7c77b 100644 --- a/tests/neg-custom-args/safe-init/override7.scala +++ b/tests/neg-custom-args/safe-init/override7.scala @@ -29,6 +29,6 @@ trait Dao(val name: String) extends Foo { trait Zen(val name: String) { val title = "Mr." - @scala.annotation.cold + @scala.annotation.icy def getName = name // error: cannot access `name` // error } \ No newline at end of file From d0856f5d88f73befdc4a439b152d4fd8b0d0eca6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 31 Oct 2018 09:50:27 +0100 Subject: [PATCH 43/83] WIP - fix exception in widening --- .../tools/dotc/transform/init/Checker.scala | 12 +++-------- .../tools/dotc/transform/init/Indexer.scala | 21 +++++-------------- .../tools/dotc/transform/init/Setting.scala | 13 +++++++++++- .../tools/dotc/transform/init/Values.scala | 13 +++++------- .../neg-custom-args/safe-init/function2.scala | 2 +- .../neg-custom-args/safe-init/function6.scala | 2 +- .../safe-init/override10.scala | 4 ++-- tests/neg-custom-args/safe-init/parent7.scala | 14 ++++++++----- 8 files changed, 38 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 0976a0d99604..32aaf7f410d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -199,11 +199,8 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => setting.ctx.warning(s"Calling the init $sym causes errors", sym.pos) res.effects.foreach(_.report) } - else if (!res.value.isHot && !sym.isCalledIn(cls)) { // effective init - setting.ctx.warning("An init method must return a full value", sym.pos) - } - else if (res.value.isHot && sym.isCalledIn(cls)) { // de facto @init - sym.annotate(defn.InitAnnotType) + else if (!res.value.widen.isHot) { + setting.ctx.warning("A dynamic init method must return a full value", sym.pos) } } @@ -245,10 +242,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val captured = Capture.analyze(cdef) val setting2 = setting.strict - val notHot = captured.keys.filter { tp => - val res = setting.analyzer.checkRef(tp)(setting2) - res.hasErrors || !res.value.widen.isHot - } + val notHot = captured.keys.filterNot(setting2.widen(_).isHot) for(key <- notHot; tree <- captured(key)) setting.ctx.warning(s"The init $sym captures " + tree.show + ".\nTry to make captured fields or methods private or final.", tree.pos) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 35fb317ae33f..89a6bea67265 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -55,7 +55,7 @@ trait Indexer { self: Analyzer => implicit val ctx: Context = setting2.ctx val env = setting2.heap(setting.env.id).asEnv val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)).withEnv(env) - setting3.widen(ddef.symbol.typeRef) { widenTree(ddef)(setting3) } + setting3.widenFor(ddef.symbol.typeRef) { widenTree(ddef)(setting3) } } } @@ -64,21 +64,10 @@ trait Indexer { self: Analyzer => indentedDebug(s"captured in ${tree.symbol}: " + captured.keys.map(_.show).mkString(", ")) val setting2 = setting.strict - val notHot = captured.keys.filter { tp => - val res = tp match { - case tp: TypeRef => // TODO: check class body - checkRef(tp.prefix)(setting2) - case _ => - checkRef(tp)(setting2) - } - indentedDebug(res.effects.mkString) - res.hasErrors || !(setting2.widen(tp) { res.value.widen }).isHot - } - - indentedDebug(s"not hot in ${tree.symbol}: " + notHot.map(_.show).mkString(", ")) - if (notHot.isEmpty) HotValue - else WarmValue(notHot.toSet, unknownDeps = false) + val value = WarmValue(captured.keys.toSet, unknownDeps = false).widen + if (!value.isHot) indentedDebug(s"not hot in ${tree.symbol}: " + value.show(setting.showSetting)) + value } def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = @@ -105,7 +94,7 @@ trait Indexer { self: Analyzer => implicit val ctx: Context = setting2.ctx val env = setting2.heap(setting.env.id).asEnv val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)).withEnv(env) - setting3.widen(vdef.symbol.typeRef) { widenTree(vdef)(setting3) } + setting3.widenFor(vdef.symbol.typeRef) { widenTree(vdef)(setting3) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index 316deff256dd..cd42102cec8f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -34,7 +34,7 @@ case class Setting( def isWidening: Boolean = wideningValues.nonEmpty - def widen(tp: Type)(value: => OpaqueValue) = + def widenFor(tp: Type)(value: => OpaqueValue): OpaqueValue = if (wideningValues.contains(tp)) HotValue else { wideningValues += tp @@ -43,5 +43,16 @@ case class Setting( res } + def widen(tp: Type): OpaqueValue = widenFor(tp) { + val res = tp match { + case tp: TypeRef => // TODO: check class body + analyzer.checkRef(tp.prefix)(this) + case _ => + analyzer.checkRef(tp)(this) + } + analyzer.indentedDebug(res.effects.mkString) + if (res.hasErrors) WarmValue() else res.value.widen(this) + } + def showSetting = ShowSetting(heap, ctx) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index b2c91823f2c3..c7c51e0e39ba 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -408,10 +408,7 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru override def widen(implicit setting: Setting) = if (unknownDeps) this else { val setting2 = setting.strict - val notHot = deps.filter { tp => - val res = setting.analyzer.checkRef(tp)(setting2) - res.hasErrors || !res.value.widen.isHot - } + val notHot = deps.filterNot(setting2.widen(_).isHot) if (notHot.isEmpty) HotValue else WarmValue(notHot.toSet, unknownDeps = false) } @@ -660,9 +657,9 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { var addAnnotation = false // dynamic calls are analysis boundary, only allow hot values if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { - if (!sym.isCalledIn(tp.classSymbol.asClass)) { // avoid duplicate annotatino + if (!sym.isCalledIn(tp.classSymbol.asClass)) { // avoid duplicate annotation // annotation on current class even though it's called above - if (setting.allowDynamic || sym.isEffectiveInit || target.is(Flags.Deferred)) + if ((setting.allowDynamic || sym.isEffectiveInit || target.is(Flags.Deferred)) && !setting.isWidening) addAnnotation = true else res += Generic(s"Dynamic call to $sym found", setting.pos) @@ -679,12 +676,12 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) res2 ++ res.effects } - else if(!target.isCalledOrAbove(tp.classSymbol.asClass)) { + else if(!target.isCalledAbove(tp.classSymbol.asClass) && !target.is(Flags.Deferred)) { // select on super, which is external assert(target.isDefinedOn(tp)) WarmValue().select(target) ++ res.effects } - else res + else HotValue.select(target) ++ res.effects if (!ret.hasErrors && addAnnotation) tp.classSymbol.addAnnotation(Annotation.Call(sym)) diff --git a/tests/neg-custom-args/safe-init/function2.scala b/tests/neg-custom-args/safe-init/function2.scala index 029b85e89bc2..682db7696989 100644 --- a/tests/neg-custom-args/safe-init/function2.scala +++ b/tests/neg-custom-args/safe-init/function2.scala @@ -1,4 +1,4 @@ -class Foo { +final class Foo { def fun: Int => Int = n => n + x.size // error: itself ok, but fun is called below fun(5) // error: select on cold value diff --git a/tests/neg-custom-args/safe-init/function6.scala b/tests/neg-custom-args/safe-init/function6.scala index 861a18ad2e3c..3f3caf69290c 100644 --- a/tests/neg-custom-args/safe-init/function6.scala +++ b/tests/neg-custom-args/safe-init/function6.scala @@ -1,7 +1,7 @@ class Foo(x: Int) { var title: String = _ - def get(msg: String): () => String = () => { + final def get(msg: String): () => String = () => { title = "Mr." title + " Jack" + ", " + msg } diff --git a/tests/neg-custom-args/safe-init/override10.scala b/tests/neg-custom-args/safe-init/override10.scala index 5f9adc33d6ed..0731b770310d 100644 --- a/tests/neg-custom-args/safe-init/override10.scala +++ b/tests/neg-custom-args/safe-init/override10.scala @@ -1,11 +1,11 @@ trait Foo { - def f: () => String = () => message + def f: () => String = () => message // error def message: String val m = f } class Bar extends Foo { val message = "hello" - f() // error + f() m() // error } \ No newline at end of file diff --git a/tests/neg-custom-args/safe-init/parent7.scala b/tests/neg-custom-args/safe-init/parent7.scala index e148e519a3a8..65e62fee361a 100644 --- a/tests/neg-custom-args/safe-init/parent7.scala +++ b/tests/neg-custom-args/safe-init/parent7.scala @@ -1,12 +1,16 @@ -trait Foo { - @scala.annotation.cold - def name: String +trait Foo(x: Int) { + @scala.annotation.icy + def name: String = "hello" def title: String + + def f: Int = x } trait Bar { this: Foo => - val message = "hello, " + name // ok: because `name` is marked cold + val message = "hello, " + name // ok: because `name` is marked icy + + println(title) // ok: deferred - println(title) // error + println(f) // error } \ No newline at end of file From 0e5b91390ccbda350fc7e90a8f7efe63885d02aa Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 31 Oct 2018 15:18:11 +0100 Subject: [PATCH 44/83] Allow select on abstract methods --- .../tools/dotc/transform/init/Values.scala | 78 ++++++++++--------- .../neg-custom-args/safe-init/function6.scala | 2 +- tests/neg-custom-args/safe-init/parent8.scala | 7 +- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index c7c51e0e39ba..e18da93b3f6c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -129,6 +129,7 @@ sealed trait Value { def isCold: Boolean = this == ColdValue def isWarm: Boolean = this.isInstanceOf[WarmValue] def isHot: Boolean = this == HotValue + def isOpaque: Boolean = this.isInstanceOf[OpaqueValue] } /** The value is absent */ @@ -644,49 +645,54 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = { val target = if (isStaticDispatch) sym else resolve(sym) - // select on self type - if (!target.exists) return { - if (sym.owner.is(Flags.Trait)) IcyValue.select(sym) - else if (tp.classSymbol.is(Flags.Trait)) WarmValue().select(sym) // classes are always init before traits - else ColdValue.select(sym) - } - - val res = Res() - - var checkParam = false - var addAnnotation = false - // dynamic calls are analysis boundary, only allow hot values - if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { - if (!sym.isCalledIn(tp.classSymbol.asClass)) { // avoid duplicate annotation - // annotation on current class even though it's called above - if ((setting.allowDynamic || sym.isEffectiveInit || target.is(Flags.Deferred)) && !setting.isWidening) - addAnnotation = true + val receiver = + if (!target.exists) { // select on self type + if (sym.owner.is(Flags.Trait)) IcyValue + else if (tp.classSymbol.is(Flags.Trait)) WarmValue() // classes are always init before traits + else ColdValue + } + else { + val cls = target.owner.asClass + if (slices.contains(cls)) + slices(cls) + else if(!target.isCalledAbove(tp.classSymbol.asClass)) { + assert(target.isDefinedOn(tp)) + WarmValue() + } else - res += Generic(s"Dynamic call to $sym found", setting.pos) + HotValue } - checkParam = sym.is(Flags.Method) && sym.info.paramNamess.flatten.nonEmpty - } + val target2 = if (target.exists) target else sym - val cls = target.owner.asClass + if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { + val res = + if (target2.is(Flags.Method)) + // dynamic calls are analysis boundary, only allow hot values + Res(value = Value.dynamicMethodValue(target2, Value.defaultFunctionValue(target2))) + else Res() + + // annotation on current class even though it's called above + if (sym.isEffectiveInit || (!receiver.isOpaque || target2.is(Flags.Deferred)) && !setting.isWidening) { + if (!receiver.isOpaque) { + val res2 = receiver.select(target2) + if (target2.is(Flags.Method)) res.value = Value.dynamicMethodValue(target2, res2.value) + else res.value = res2.value + res ++= res2.effects + } - val ret = - if (slices.contains(cls)) { - val res2 = slices(cls).select(target) - if (checkParam) res2.value = Value.dynamicMethodValue(target, res2.value) - res2 ++ res.effects + if (!res.hasErrors && !sym.isCalledIn(tp.classSymbol.asClass)) + tp.classSymbol.addAnnotation(Annotation.Call(sym)) + + res } - else if(!target.isCalledAbove(tp.classSymbol.asClass) && !target.is(Flags.Deferred)) { - // select on super, which is external - assert(target.isDefinedOn(tp)) - WarmValue().select(target) ++ res.effects + else if (!setting.isWidening) receiver.select(target2) + else { + res += Generic(s"Dynamic call to $sym found", setting.pos) // useful in widening + res } - else HotValue.select(target) ++ res.effects - - if (!ret.hasErrors && addAnnotation) - tp.classSymbol.addAnnotation(Annotation.Call(sym)) - - ret + } + else receiver.select(target2) } def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = { diff --git a/tests/neg-custom-args/safe-init/function6.scala b/tests/neg-custom-args/safe-init/function6.scala index 3f3caf69290c..0b922139593d 100644 --- a/tests/neg-custom-args/safe-init/function6.scala +++ b/tests/neg-custom-args/safe-init/function6.scala @@ -17,7 +17,7 @@ class Foo(x: Int) { class Foo2(x: Int) { var title: String = _ - def get(msg: String): () => String = () => { + final def get(msg: String): () => String = () => { title = "Mr." title + " Jack" + ", " + msg } diff --git a/tests/neg-custom-args/safe-init/parent8.scala b/tests/neg-custom-args/safe-init/parent8.scala index 21ed4794e1a1..dc79b91e5d7f 100644 --- a/tests/neg-custom-args/safe-init/parent8.scala +++ b/tests/neg-custom-args/safe-init/parent8.scala @@ -7,5 +7,10 @@ abstract class Foo { trait Bar { this: Foo => val message = "hello, " + name // ok: because `Foo` is a class - println(title) // error + println(title) // ok: title is abstract +} + +class Qux extends Foo with Bar { + val x = "hello" + def title = x // error // error } From 7ca3a043b4c3106e1f6b7cc0f4c1f69633d9d861 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 1 Nov 2018 11:07:00 +0100 Subject: [PATCH 45/83] Check capturing of TypeRef in New --- .../tools/dotc/transform/init/Analyzer.scala | 4 +- .../tools/dotc/transform/init/Checker.scala | 22 ++++----- .../tools/dotc/transform/init/Indexer.scala | 14 +++--- .../tools/dotc/transform/init/Setting.scala | 48 +++++++++++-------- .../tools/dotc/transform/init/Values.scala | 28 +++++------ 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 7af45aead5b7..65001820f0f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -74,11 +74,11 @@ class Analyzer extends Indexer { analyzer => setting.env.select(tp.symbol) case tp @ TermRef(prefix, _) => val res = checkRef(prefix) - res.value.tryWiden.select(tp.symbol) + res.value.select(tp.symbol) case tp @ ThisType(tref) => val cls = tref.symbol if (cls.is(Package)) Res() // Dotty represents package path by ThisType - else if (setting.env.contains(cls)) Res(value = setting.env(cls)) + else if (setting.env.contains(cls)) Res(value = setting.env(cls).tryWiden) else { // ThisType used outside of class scope, can happen for objects // see tests/pos/t2712-7.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 32aaf7f410d3..856f72939829 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -138,7 +138,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (!sym.isEffectiveCold) return val root = Heap.createRootEnv - val setting: Setting = Setting(root, sym.pos, ctx, analyzer).strict + val setting: Setting = Setting(root, sym.pos, ctx, analyzer, isWidening = true) indexOuter(cls)(setting) if (sym.isIcy) root.add(cls, IcyValue) else root.add(cls, ColdValue) @@ -160,20 +160,20 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => if (!sym.isEffectiveCold) return val root = Heap.createRootEnv - val setting: Setting = Setting(root, sym.pos, ctx, analyzer) + val setting: Setting = Setting(root, sym.pos, ctx, analyzer, isWidening = true) indexOuter(cls)(setting) if (sym.isCold) root.add(cls, IcyValue) else root.add(cls, ColdValue) - val value = analyzer.lazyValue(vdef)(setting.strict) - val res = value.apply(i => HotValue, i => NoPosition)(setting.strict) + val value = analyzer.lazyValue(vdef)(setting) + val res = value.apply(i => HotValue, i => NoPosition)(setting) if (res.hasErrors) { ctx.warning("Forcing cold lazy value causes errors", sym.pos) res.effects.foreach(_.report) } else { - val value = res.value.widen(setting.strict) + val value = res.value.widen(setting) if (!value.isHot) ctx.warning("Cold lazy value must return a full value", sym.pos) } } @@ -199,7 +199,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => setting.ctx.warning(s"Calling the init $sym causes errors", sym.pos) res.effects.foreach(_.report) } - else if (!res.value.widen.isHot) { + else if (!res.value.widen(setting.widening).isHot) { setting.ctx.warning("A dynamic init method must return a full value", sym.pos) } } @@ -214,7 +214,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => res.effects.foreach(_.report) } else { - val value = res.value.widen(setting.strict) + val value = res.value.widen(setting.widening) if (!value.isHot) setting.ctx.warning("Init lazy value must return a full value", sym.pos) } } @@ -223,7 +223,7 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => val sym = vdef.symbol if (sym.is(Flags.PrivateOrLocal)) return - val actual = obj.select(sym, isStaticDispatch = true).value.widen(setting.strict) + val actual = obj.select(sym, isStaticDispatch = true).value.widen(setting.widening) if (actual.isCold) sym.annotate(defn.ColdAnnotType) else if (actual.isWarm) sym.annotate(defn.WarmAnnotType) @@ -238,10 +238,10 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => def checkClassDef(cdef: tpd.TypeDef)(implicit setting: Setting): Unit = { val sym = cdef.symbol if (sym.isInit) { - val value = setting.analyzer.widenTree(cdef) + val setting2 = setting.widening + val value = setting2.analyzer.widenTree(cdef)(setting2) - val captured = Capture.analyze(cdef) - val setting2 = setting.strict + val captured = Capture.analyze(cdef)(setting2) val notHot = captured.keys.filterNot(setting2.widen(_).isHot) for(key <- notHot; tree <- captured(key)) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 89a6bea67265..885f43a59835 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -28,7 +28,7 @@ trait Indexer { self: Analyzer => import tpd._ def methodValue(ddef: DefDef)(implicit setting: Setting): FunctionValue = - new FunctionValue { + new FunctionValue { fv => def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx @@ -55,23 +55,23 @@ trait Indexer { self: Analyzer => implicit val ctx: Context = setting2.ctx val env = setting2.heap(setting.env.id).asEnv val setting3 = setting2.withCtx(setting2.ctx.withOwner(ddef.symbol)).withEnv(env) - setting3.widenFor(ddef.symbol.typeRef) { widenTree(ddef)(setting3) } + setting3.widenFor(fv) { widenTree(ddef)(setting3) } } + + override def show(implicit setting: ShowSetting): String = ddef.symbol.show } def widenTree(tree: Tree)(implicit setting: Setting): OpaqueValue = { val captured = Capture.analyze(tree) indentedDebug(s"captured in ${tree.symbol}: " + captured.keys.map(_.show).mkString(", ")) - val setting2 = setting.strict - val value = WarmValue(captured.keys.toSet, unknownDeps = false).widen if (!value.isHot) indentedDebug(s"not hot in ${tree.symbol}: " + value.show(setting.showSetting)) value } def lazyValue(vdef: ValDef)(implicit setting: Setting): LazyValue = - new LazyValue { + new LazyValue { lz => def apply(values: Int => Value, argPos: Int => Position)(implicit setting2: Setting): Res = { // TODO: implicit ambiguities implicit val ctx: Context = setting2.ctx @@ -94,8 +94,10 @@ trait Indexer { self: Analyzer => implicit val ctx: Context = setting2.ctx val env = setting2.heap(setting.env.id).asEnv val setting3 = setting2.withCtx(setting2.ctx.withOwner(vdef.symbol)).withEnv(env) - setting3.widenFor(vdef.symbol.typeRef) { widenTree(vdef)(setting3) } + setting3.widenFor(lz) { widenTree(vdef)(setting3) } } + + override def show(implicit setting: ShowSetting): String = vdef.symbol.show } /** Index local definitions */ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index cd42102cec8f..6db40547ef33 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -6,6 +6,7 @@ import core._ import Symbols._ import Types._ import Contexts.Context +import util.SimpleIdentitySet import util.Positions._ import config.Printers.init.{ println => debug } @@ -16,11 +17,9 @@ case class Setting( pos: Position, ctx: Context, analyzer: Analyzer, - allowDynamic: Boolean = true, - forceLazy: Boolean = true, - callParameterless: Boolean = true, - wideningValues: mutable.Set[Type] = mutable.Set.empty) { - def strict: Setting = copy(allowDynamic = false, forceLazy = false, callParameterless = false) + isWidening: Boolean = false, + var wideningValues: SimpleIdentitySet[Value] = SimpleIdentitySet.empty) { + def widening: Setting = copy(isWidening = true) def heap: Heap = env.heap def withPos(position: Position) = copy(pos = position) def withEnv(ienv: Env) = copy(env = ienv) @@ -32,26 +31,35 @@ case class Setting( copy(env = env2.asEnv) } - def isWidening: Boolean = wideningValues.nonEmpty - - def widenFor(tp: Type)(value: => OpaqueValue): OpaqueValue = - if (wideningValues.contains(tp)) HotValue + def widenFor(v: Value)(value: => OpaqueValue): OpaqueValue = + if (wideningValues.contains(v)) HotValue else { - wideningValues += tp + analyzer.indentedDebug(s"widening ${v.show(this.showSetting)} = ?") + wideningValues = wideningValues + v val res = value - wideningValues -= tp + wideningValues = wideningValues - v + analyzer.indentedDebug(s"widening ${v.show(this.showSetting)} = " + res.show(this.showSetting)) res } - def widen(tp: Type): OpaqueValue = widenFor(tp) { - val res = tp match { - case tp: TypeRef => // TODO: check class body - analyzer.checkRef(tp.prefix)(this) - case _ => - analyzer.checkRef(tp)(this) - } - analyzer.indentedDebug(res.effects.mkString) - if (res.hasErrors) WarmValue() else res.value.widen(this) + def widen(tp: Type): OpaqueValue = tp match { + case tp: TypeRef => // TODO: check class body + val prefixRes = analyzer.checkRef(tp.prefix)(this) + if (prefixRes.hasErrors) WarmValue() + else { + implicit val ctx: Context = this.ctx + val constr = tp.classSymbol.primaryConstructor + val obj = new ObjectValue(tp, open = false) + val setting = this.freshHeap + val paramCount = constr.info.paramInfoss.flatten.size + val args = constr.info.paramInfoss.flatten.map(_.value) + val poss = List.fill(paramCount)(NoPosition) + val res = prefixRes.value.init(constr, args, poss, obj)(setting) + if (res.hasErrors) WarmValue() else obj.widen(setting) + } + case _ => + val res = analyzer.checkRef(tp)(this) + if (res.hasErrors) WarmValue() else res.value.widen(this) } def showSetting = ShowSetting(heap, ctx) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index e18da93b3f6c..3accce5afedb 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -35,7 +35,7 @@ object Value { paramInfos.zipWithIndex.foreach { case (tp, index) => val value = scala.util.Try(values(index)).getOrElse(HotValue) val pos = scala.util.Try(argPos(index)).getOrElse(NoPosition) - val wValue = value.widen + val wValue = value.widen(setting.widening) if (!wValue.isHot && (onlyHot || !tp.value.isCold) || wValue.isIcy) // warm objects only leak as cold, for safety and simplicity return Res(effects = Vector(Generic(message(wValue), pos))) } @@ -51,7 +51,7 @@ object Value { checkParams(methSym, paramInfos, values, argPos, onlyHot = true) } - def widen(implicit setting: Setting) = ??? + def widen(implicit setting: Setting) = ColdValue // could be reached by tryWiden on funtions } } @@ -65,7 +65,7 @@ object Value { value.apply(values, argPos) ++ effs } - def widen(implicit setting: Setting) = ??? + def widen(implicit setting: Setting) = value.widen } } } @@ -120,7 +120,7 @@ sealed trait Value { def widen(implicit setting: Setting): OpaqueValue def tryWiden(implicit setting: Setting): Value = - if (!setting.isWidening && this.widen.isHot) HotValue else this + if (this.widen(setting.widening).isHot) HotValue else this def asSlice(implicit setting: Setting): SliceRep = setting.heap(this.asInstanceOf[SliceValue].id).asSlice @@ -408,8 +408,7 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru override def widen(implicit setting: Setting) = if (unknownDeps) this else { - val setting2 = setting.strict - val notHot = deps.filterNot(setting2.widen(_).isHot) + val notHot = deps.filterNot(setting.widen(_).isHot) if (notHot.isEmpty) HotValue else WarmValue(notHot.toSet, unknownDeps = false) } @@ -568,14 +567,14 @@ class SliceValue(val id: Int) extends SingleValue { val value = slice(sym) if (sym.is(Flags.Lazy)) { - if (value.isInstanceOf[LazyValue] && setting.forceLazy) { + if (value.isInstanceOf[LazyValue] && !setting.isWidening) { val res = value(Nil, Nil) slice(sym) = res.value res } else Res(value = value) } - else if (sym.is(Flags.Method) && setting.callParameterless) { + else if (sym.is(Flags.Method) && !setting.isWidening) { if (sym.info.isParameterless) { // parameter-less call value(Nil, Nil) } @@ -686,11 +685,11 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { res } - else if (!setting.isWidening) receiver.select(target2) - else { + else if (setting.isWidening && !sym.isCalledIn(tp.classSymbol.asClass)) { res += Generic(s"Dynamic call to $sym found", setting.pos) // useful in widening res } + else receiver.select(target2) } else receiver.select(target2) } @@ -724,12 +723,13 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } } - def widen(implicit setting: Setting): OpaqueValue = { + def widen(implicit setting: Setting): OpaqueValue = if (open) ColdValue - else slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => - v.widen.join(acc) + else setting.widenFor(this) { + slices.values.foldLeft(HotValue: OpaqueValue) { (acc, v) => + v.widen.join(acc) + } } - } def show(implicit setting: ShowSetting): String = { val body = slices.map { case (k, v) => "[" +k.show + "]" + setting.indent(v.show(setting)) }.mkString("\n") From 6d60f5ed20ccc668170e7863d68a9d2e6eba2533 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 1 Nov 2018 14:52:56 +0100 Subject: [PATCH 46/83] Introduce class values --- .../tools/dotc/transform/init/Analyzer.scala | 24 ++-- .../tools/dotc/transform/init/Capture.scala | 2 +- .../tools/dotc/transform/init/Checker.scala | 14 +- .../tools/dotc/transform/init/Heap.scala | 29 ---- .../tools/dotc/transform/init/Indexer.scala | 35 ++++- .../tools/dotc/transform/init/Setting.scala | 21 +-- .../tools/dotc/transform/init/Values.scala | 125 ++++++++---------- tests/neg-custom-args/safe-init/inner14.scala | 13 ++ 8 files changed, 123 insertions(+), 140 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/inner14.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 65001820f0f0..2f3ca843fa20 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -66,15 +66,20 @@ class Analyzer extends Indexer { analyzer => res } - def checkRef(tp: Type)(implicit setting: Setting): Res = trace("checking " + tp.show)(tp match { + def checkRef(tp: Type)(implicit setting: Setting): Res = trace("checking " + tp.show)(tp.dealias match { case tp : TermRef if tp.symbol.is(Module) && setting.ctx.owner.enclosedIn(tp.symbol.moduleClass) => // self reference by name: object O { ... O.xxx } checkRef(ThisType.raw(tp.symbol.moduleClass.typeRef)) case tp @ TermRef(NoPrefix, _) => setting.env.select(tp.symbol) + case tp @ TypeRef(NoPrefix, _) => + setting.env.select(tp.symbol) case tp @ TermRef(prefix, _) => val res = checkRef(prefix) res.value.select(tp.symbol) + case tp @ TypeRef(prefix, _) => + val res = checkRef(prefix) + res.value.select(tp.symbol) case tp @ ThisType(tref) => val cls = tref.symbol if (cls.is(Package)) Res() // Dotty represents package path by ThisType @@ -85,6 +90,8 @@ class Analyzer extends Indexer { analyzer => assert(cls.is(Flags.Module) && !setting.ctx.owner.enclosedIn(cls)) Res() } + case tp: AppliedType => + checkRef(tp.tycon) }) def checkClosure(sym: Symbol, tree: Tree)(implicit setting: Setting): Res = { @@ -198,18 +205,9 @@ class Analyzer extends Indexer { analyzer => if (effs.nonEmpty) return Res(effs) - def toPrefix(tp: Type): Type = tp match { - case AppliedType(tycon, _) => toPrefix(tycon.dealias) - case tp: TypeRef => tp.prefix - } - - val prefix = toPrefix(tp) - if (prefix == NoPrefix) setting.env.init(init, argValues, args.map(_.pos), obj) - else { - val prefixRes = checkRef(prefix) - if (prefixRes.hasErrors) return prefixRes - prefixRes.value.init(init, argValues, args.map(_.pos), obj) - } + val classRes = checkRef(tp) + if (classRes.hasErrors) classRes + else classRes.value.init(init, argValues, args.map(_.pos), obj) } def checkParents(cls: ClassSymbol, parents: List[Tree], obj: ObjectValue)(implicit setting: Setting): Res = { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala index bf7696f093b5..8705e92bab5b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Capture.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Capture.scala @@ -53,6 +53,7 @@ object Capture { def traverse(tree: Tree)(implicit ctx: Context) = try { //debug tree match { + case tree if tree.isType =>// ignore all type trees case tree: Ident => check(tree) case Trees.Select(ths: This, _) => @@ -63,7 +64,6 @@ object Capture { check(tree) case tree: New => check(tree) - case tree if tree.isType =>// ignore all type trees case _ => traverseChildren(tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 856f72939829..8746a6058040 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -46,8 +46,10 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => override def phaseName: String = Checker.name - override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = { - val cls = ctx.owner.asClass + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = { + if (!tree.isClassDef) return tree + + val cls = tree.symbol.asClass val self = cls.thisType // ignore init checking if `@unchecked` @@ -91,7 +93,9 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => tree } - def checkInit(cls: ClassSymbol, tmpl: tpd.Template)(implicit ctx: Context) = { + def checkInit(cls: ClassSymbol, cdef: tpd.TypeDef)(implicit ctx: Context) = { + val tmpl = cdef.rhs.asInstanceOf[tpd.Template] + debug("*************************************") debug("checking " + cls.show) debug("*************************************") @@ -109,14 +113,14 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => // and whether children are possible in other modules. // for recursive usage - root.addClassDef(cls, tmpl) indexOuter(cls)(setting) + val classValue = analyzer.classValue(cdef)(setting) // init check val constr = tmpl.constr val values = constr.vparamss.flatten.map { param => param.tpe.widen.value } val poss = constr.vparamss.flatten.map(_.pos) - val res = root.init(constr.symbol, values, poss, obj)(setting) + val res = classValue.init(constr.symbol, values, poss, obj)(setting) val slice = obj.slices(cls).asSlice(setting) res.effects.foreach(_.report) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index 384e26c76be1..bc05074b2a82 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -39,9 +39,6 @@ object Heap { class RootEnv extends Env(-1) { override def contains(sym: Symbol): Boolean = _syms.contains(sym) - - override def containsClass(cls: ClassSymbol): Boolean = - _classInfos.contains(cls) } def createRootEnv: Env = { @@ -124,16 +121,6 @@ class Env(outerId: Int) extends HeapEntry { /** local symbols defined in current scope */ protected var _syms: Map[Symbol, Value] = Map() - /** local class definitions */ - protected var _classInfos: Map[ClassSymbol, Template] = Map() - def addClassDef(cls: ClassSymbol, tmpl: Template) = - _classInfos = _classInfos.updated(cls, tmpl) - def containsClass(cls: ClassSymbol): Boolean = - _classInfos.contains(cls) || outer.containsClass(cls) - def getClassDef(cls: ClassSymbol): Template = - if (_classInfos.contains(cls)) _classInfos(cls) - else outer.getClassDef(cls) - def outer: Env = heap(outerId).asInstanceOf[Env] def fresh(heap: Heap = this.heap): Env = { @@ -214,7 +201,6 @@ class Env(outerId: Int) extends HeapEntry { Res(value = value) } } - else if (sym.isClass && this.containsClass(sym.asClass)) Res() else { // How do we know the class/method/field does not capture/use a cold/warm outer? // If method/field exist, then the outer class beyond the method/field is hot, @@ -222,15 +208,6 @@ class Env(outerId: Int) extends HeapEntry { HotValue.select(sym) } - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val cls = constr.owner.asClass - if (this.containsClass(cls)) { - val tmpl = this.getClassDef(cls) - setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(this)) - } - else HotValue.init(constr, values, argPos, obj) - } - def show(implicit setting: ShowSetting): String = { def members = _syms.map { case (k, v) => k.show + " ->" + setting.indent(v.show(setting), tabs = 2) }.mkString("\n") (if (outerId > 0) outer.show(setting) + "\n" else "") ++ @@ -245,12 +222,6 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo def innerEnv: Env = heap(innerEnvId).asEnv - /** inner class definitions */ - private var _classInfos: Map[ClassSymbol, Template] = Map() - def add(cls: ClassSymbol, info: Template) = _classInfos = - _classInfos.updated(cls, info) - def classInfos: Map[ClassSymbol, Template] = _classInfos - /** methods and fields of the slice */ private var _syms: Map[Symbol, Value] = Map() diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 885f43a59835..0849bb47ad78 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -61,6 +61,35 @@ trait Indexer { self: Analyzer => override def show(implicit setting: ShowSetting): String = ddef.symbol.show } + def classValue(cdef: TypeDef)(implicit setting: Setting): ClassValue = + new ClassValue { cv => + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting2: Setting): Res = { + // TODO: implicit ambiguities + implicit val ctx: Context = setting2.ctx + if (isChecking(cdef.symbol)) { + // TODO: check if fixed point has reached. But the domain is infinite, thus non-terminating. + debug(s"recursive instantiation of ${cdef.symbol} found") + Res() + } + else { + val tmpl = cdef.rhs.asInstanceOf[Template] + val env2 = setting.env.fresh(setting2.heap) + val setting3 = setting2.withCtx(setting2.ctx.withOwner(cdef.symbol)).withEnv(env2) + self.init(constr, tmpl, values, argPos, obj)(setting3) + } + } + + def widen(implicit setting2: Setting) = { + // TODO: implicit ambiguities + implicit val ctx: Context = setting2.ctx + val env = setting2.heap(setting.env.id).asEnv + val setting3 = setting2.withCtx(setting2.ctx.withOwner(cdef.symbol)).withEnv(env) + setting3.widenFor(cv) { widenTree(cdef)(setting3) } + } + + override def show(implicit setting: ShowSetting): String = cdef.symbol.show + } + def widenTree(tree: Tree)(implicit setting: Setting): OpaqueValue = { val captured = Capture.analyze(tree) indentedDebug(s"captured in ${tree.symbol}: " + captured.keys.map(_.show).mkString(", ")) @@ -109,8 +138,7 @@ trait Indexer { self: Analyzer => case vdef: ValDef => setting.env.add(vdef.symbol, NoValue) case tdef: TypeDef if tdef.isClassDef => - // class has to be handled differently because of inheritance - setting.env.addClassDef(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) + setting.env.add(tdef.symbol.asClass, classValue(tdef)) case _ => } @@ -127,8 +155,7 @@ trait Indexer { self: Analyzer => val value = if (vdef.symbol.isInit || vdef.symbol.is(Deferred)) HotValue else NoValue slice.add(vdef.symbol, value) case tdef: TypeDef if tdef.isClassDef => - // class has to be handled differently because of inheritance - slice.add(tdef.symbol.asClass, tdef.rhs.asInstanceOf[Template]) + slice.add(tdef.symbol.asClass, classValue(tdef)(setting.withEnv(slice.innerEnv))) case _ => } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala index 6db40547ef33..0ead068e07d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Setting.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Setting.scala @@ -42,24 +42,9 @@ case class Setting( res } - def widen(tp: Type): OpaqueValue = tp match { - case tp: TypeRef => // TODO: check class body - val prefixRes = analyzer.checkRef(tp.prefix)(this) - if (prefixRes.hasErrors) WarmValue() - else { - implicit val ctx: Context = this.ctx - val constr = tp.classSymbol.primaryConstructor - val obj = new ObjectValue(tp, open = false) - val setting = this.freshHeap - val paramCount = constr.info.paramInfoss.flatten.size - val args = constr.info.paramInfoss.flatten.map(_.value) - val poss = List.fill(paramCount)(NoPosition) - val res = prefixRes.value.init(constr, args, poss, obj)(setting) - if (res.hasErrors) WarmValue() else obj.widen(setting) - } - case _ => - val res = analyzer.checkRef(tp)(this) - if (res.hasErrors) WarmValue() else res.value.widen(this) + def widen(tp: Type): OpaqueValue = { + val res = analyzer.checkRef(tp)(this) + if (res.hasErrors) WarmValue() else res.value.widen(this) } def showSetting = ShowSetting(heap, ctx) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Values.scala b/compiler/src/dotty/tools/dotc/transform/init/Values.scala index 3accce5afedb..f8232f3d7c6f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Values.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Values.scala @@ -51,7 +51,31 @@ object Value { checkParams(methSym, paramInfos, values, argPos, onlyHot = true) } - def widen(implicit setting: Setting) = ColdValue // could be reached by tryWiden on funtions + def widen(implicit setting: Setting) = HotValue // could be reached by tryWiden on funtions + } + } + + def defaultClassValue(classSym: Symbol, prefix: OpaqueValue)(implicit setting: Setting): Value = { + assert(classSym.isClass) + new ClassValue { + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { + val cls = constr.owner.asClass + val paramInfos = constr.info.paramInfoss.flatten + val res = Value.checkParams(cls, paramInfos, values, argPos) + if (res.hasErrors) return res + + val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) + val value = args.foldLeft(prefix) { (acc, v) => + acc.join(v.widen) + } + + if (cls == obj.tp.classSymbol && !obj.open) obj.add(cls, value.meet(WarmValue())) + else if (!value.isHot) obj.add(cls, WarmValue()) + + Res() + } + + def widen(implicit setting: Setting) = prefix } } @@ -219,6 +243,7 @@ abstract sealed class OpaqueValue extends SingleValue { object HotValue extends OpaqueValue { def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = if (sym.is(Flags.Method)) Res(value = Value.defaultFunctionValue(sym)) + else if (sym.isClass) Res(value = Value.defaultClassValue(sym, HotValue)) else Res() def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = @@ -226,22 +251,7 @@ object HotValue extends OpaqueValue { Res(effects = Vector(Generic("Cannot assign an object under initialization to a fully initialized object", setting.pos))) else Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val cls = constr.owner.asClass - val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(cls, paramInfos, values, argPos) - if (res.hasErrors) return res - - val args = (0 until paramInfos.size).map(i => scala.util.Try(values(i)).getOrElse(HotValue)) - val argsV = args.foldLeft(HotValue: OpaqueValue) { (acc, v) => - acc.join(v.widen) - } - - if (cls == obj.tp.classSymbol && !obj.open) obj.add(cls, argsV.meet(WarmValue())) - else if (!argsV.isHot) obj.add(cls, WarmValue()) - - Res() - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? def show(implicit setting: ShowSetting): String = "Hot" @@ -279,6 +289,8 @@ object IcyValue extends OpaqueValue { else if (sym.isClass) { if (!sym.isIcy) res += Generic(s"The nested $sym should be marked as `@icy` in order to be instantiated", setting.pos) + + res.value = Value.defaultClassValue(sym, IcyValue) } else { // field select res += Generic(s"$sym may not be initialized", setting.pos) @@ -317,6 +329,8 @@ object ColdValue extends OpaqueValue { else if (sym.isClass) { if (!sym.isCold) res += Generic(s"The nested $sym should be marked as `@cold` in order to be instantiated", setting.pos) + + res.value = Value.defaultClassValue(sym, ColdValue) } else { // field select if (!sym.isClassParam) @@ -329,22 +343,7 @@ object ColdValue extends OpaqueValue { /** assign to cold is always fine? */ def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = Res() - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos) - if (res.hasErrors) return res - - val cls = constr.owner.asClass - if (!cls.isCold) { - res += Generic(s"The nested $cls should be marked as `@cold` in order to be instantiated", setting.pos) - res.value = HotValue - return res - } - - obj.add(cls, WarmValue()) - - Res() - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? def show(implicit setting: ShowSetting): String = "Cold" @@ -372,6 +371,13 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru res.value = sym.value } + else if (sym.isClass) { + if (!sym.isInit && !sym.isCold && !sym.isWarm) + res += Generic(s"The nested $sym should be marked as `@init` in order to be instantiated", setting.pos) + + val prefix = if (sym.isInit) HotValue else WarmValue() + res.value = Value.defaultClassValue(sym, prefix) + } else { res.value = sym.value } @@ -386,25 +392,7 @@ case class WarmValue(val deps: Set[Type] = Set.empty, unknownDeps: Boolean = tru else Res() } - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val paramInfos = constr.info.paramInfoss.flatten - val res = Value.checkParams(constr.owner, paramInfos, values, argPos) - if (res.hasErrors) return res - - val cls = constr.owner.asClass - if (cls.isInit) { - if (values.exists(!_.widen.isHot)) obj.add(cls, WarmValue()) - Res() - } - else if (cls.isCold || cls.isWarm) { - obj.add(cls, WarmValue()) - Res() - } - else { - res += Generic(s"The nested $cls should be marked as `@init` in order to be instantiated", setting.pos) - res - } - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? override def widen(implicit setting: Setting) = if (unknownDeps) this else { @@ -597,12 +585,7 @@ class SliceValue(val id: Int) extends SingleValue { Res() } - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val cls = constr.owner.asClass - val slice = this.asSlice - val tmpl = slice.classInfos(cls) - setting.analyzer.init(constr, tmpl, values, argPos, obj)(setting.withEnv(slice.innerEnv)) - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? def widen(implicit setting: Setting): OpaqueValue = this.asSlice.widen @@ -617,6 +600,18 @@ class SliceValue(val id: Int) extends SingleValue { def show(implicit setting: ShowSetting): String = setting.heap(id).asSlice.show(setting) } +/** A class value */ +abstract class ClassValue extends SingleValue { + // not supported + def apply(values: Int => Value, argPos: Int => Position)(implicit setting: Setting): Res = ??? + def select(sym: Symbol, isStaticDispatch: Boolean)(implicit setting: Setting): Res = ??? + def assign(sym: Symbol, value: Value)(implicit setting: Setting): Res = ??? + + def show(implicit setting: ShowSetting): String = toString + + override def toString: String = "ClassValue@" + hashCode +} + class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { /** slices of the object */ private var _slices: Map[ClassSymbol, Value] = Map() @@ -664,7 +659,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { val target2 = if (target.exists) target else sym - if (open && !isStaticDispatch && !sym.isEffectivelyFinal) { + if (open && !isStaticDispatch && !sym.isEffectivelyFinal && !sym.isClass) { val res = if (target2.is(Flags.Method)) // dynamic calls are analysis boundary, only allow hot values @@ -711,17 +706,7 @@ class ObjectValue(val tp: Type, val open: Boolean = false) extends SingleValue { } } - def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = { - val cls = constr.owner.asClass - val outerCls = cls.owner.asClass - if (slices.contains(outerCls)) { - slices(outerCls).init(constr, values, argPos, obj) - } - else { - val value = if (cls.isDefinedOn(tp)) WarmValue() else ColdValue - value.init(constr, values, argPos, obj) - } - } + def init(constr: Symbol, values: List[Value], argPos: List[Position], obj: ObjectValue)(implicit setting: Setting): Res = ??? def widen(implicit setting: Setting): OpaqueValue = if (open) ColdValue diff --git a/tests/neg-custom-args/safe-init/inner14.scala b/tests/neg-custom-args/safe-init/inner14.scala new file mode 100644 index 000000000000..0adc480a56f3 --- /dev/null +++ b/tests/neg-custom-args/safe-init/inner14.scala @@ -0,0 +1,13 @@ +final class A { + val x = 10 + + class Inner { + def f(n: Int) = println(new Inner) + val y = (n: Int) => f(20) + } + + println(new Inner) + + val y = z // error + val z = 10 +} \ No newline at end of file From 00ff986dd01c49a3b30ac1149e946fcbe10f0fa4 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 1 Nov 2018 15:21:42 +0100 Subject: [PATCH 47/83] Auto infer @init of inner class --- .../tools/dotc/transform/init/Checker.scala | 4 ++++ .../tools/dotc/transform/init/Heap.scala | 2 +- tests/neg-custom-args/safe-init/inner15.scala | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/safe-init/inner15.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 8746a6058040..08a15b56f711 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -251,6 +251,10 @@ class Checker extends MiniPhase with IdentityDenotTransformer { thisPhase => for(key <- notHot; tree <- captured(key)) setting.ctx.warning(s"The init $sym captures " + tree.show + ".\nTry to make captured fields or methods private or final.", tree.pos) } + else { + val classValue = obj.select(sym).value.widen(setting.widening) + if (classValue.isHot) sym.annotate(defn.InitAnnotType) + } } tmpl.body.foreach { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala index bc05074b2a82..c5b3ffc0b37b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Heap.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Heap.scala @@ -286,7 +286,7 @@ class SliceRep(val cls: ClassSymbol, innerEnvId: Int) extends HeapEntry with Clo if (symbols.exists { case (sym, v) => unInitField(sym, v) }) ColdValue else if (symbols.exists { case (sym, v) => capturingDef(sym, v) }) ColdValue + else if (symbols.exists { case (sym, v) => sym.isClass && !v.widen.isHot }) ColdValue else HotValue - // TODO: check inner classes } } diff --git a/tests/neg-custom-args/safe-init/inner15.scala b/tests/neg-custom-args/safe-init/inner15.scala new file mode 100644 index 000000000000..b18708e14a7f --- /dev/null +++ b/tests/neg-custom-args/safe-init/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 { + println(new Inner1) + println(new Inner2) // error + + override val x = "world" +} \ No newline at end of file From 61e8de3acfe83d54c74bd266e87edfefb7c1a898 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 1 Nov 2018 15:33:48 +0100 Subject: [PATCH 48/83] No blow up for Flags --- .../tools/dotc/transform/init/Analyzer.scala | 10 +- .../tools/dotc/transform/init/Indexer.scala | 6 +- .../neg-custom-args/safe-init/explosion.scala | 382 ++++++++++++++++++ 3 files changed, 388 insertions(+), 10 deletions(-) create mode 100644 tests/neg-custom-args/safe-init/explosion.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala index 2f3ca843fa20..1ce8659fcc2d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Analyzer.scala @@ -253,13 +253,9 @@ class Analyzer extends Indexer { analyzer => def checkNew(tree: Tree, tref: TypeRef, init: Symbol, argss: List[List[Tree]])(implicit setting: Setting): Res = { val obj = new ObjectValue(tree.tpe, open = false) val res = checkInit(obj.tp, init, argss, obj) - if (obj.slices.isEmpty) { - res.copy(value = HotValue) - } - else { - if (res.hasErrors) res.effects = Vector(Instantiate(tree.tpe.classSymbol, res.effects, tree.pos)) - res.copy(value = obj) - } + + if (res.hasErrors) res.effects = Vector(Instantiate(tree.tpe.classSymbol, res.effects, tree.pos)) + res.copy(value = obj.tryWiden) } def checkSuper(tree: Tree, supert: Super)(implicit setting: Setting): Res = { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala index 0849bb47ad78..42f3f16b8604 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Indexer.scala @@ -58,7 +58,7 @@ trait Indexer { self: Analyzer => setting3.widenFor(fv) { widenTree(ddef)(setting3) } } - override def show(implicit setting: ShowSetting): String = ddef.symbol.show + override def show(implicit setting: ShowSetting): String = ddef.symbol(setting.ctx).show(setting.ctx) } def classValue(cdef: TypeDef)(implicit setting: Setting): ClassValue = @@ -87,7 +87,7 @@ trait Indexer { self: Analyzer => setting3.widenFor(cv) { widenTree(cdef)(setting3) } } - override def show(implicit setting: ShowSetting): String = cdef.symbol.show + override def show(implicit setting: ShowSetting): String = cdef.symbol(setting.ctx).show(setting.ctx) } def widenTree(tree: Tree)(implicit setting: Setting): OpaqueValue = { @@ -126,7 +126,7 @@ trait Indexer { self: Analyzer => setting3.widenFor(lz) { widenTree(vdef)(setting3) } } - override def show(implicit setting: ShowSetting): String = vdef.symbol.show + override def show(implicit setting: ShowSetting): String = vdef.symbol(setting.ctx).show(setting.ctx) } /** Index local definitions */ diff --git a/tests/neg-custom-args/safe-init/explosion.scala b/tests/neg-custom-args/safe-init/explosion.scala new file mode 100644 index 000000000000..63dfd2ad4e3e --- /dev/null +++ b/tests/neg-custom-args/safe-init/explosion.scala @@ -0,0 +1,382 @@ +import scala.language.implicitConversions + +object Flags { + + /** A FlagSet represents a set of flags. Flags are encoded as follows: + * The first two bits indicate whether a flagset applies to terms, + * to types, or to both. Bits 2..63 are available for properties + * and can be doubly used for terms and types. + */ + case class FlagSet(val bits: Long) extends AnyVal { + + /** The union of this flag set and the given flag set + * Combining two FlagSets with `|` will give a FlagSet + * that has the intersection of the applicability to terms/types + * of the two flag sets. It is checked that the intersection is not empty. + */ + def | (that: FlagSet): FlagSet = + if (bits == 0) that + else if (that.bits == 0) this + else { + val tbits = bits & that.bits & KINDFLAGS + if (tbits == 0) + assert(false, s"illegal flagset combination: $this and $that") + FlagSet(tbits | ((this.bits | that.bits) & ~KINDFLAGS)) + } + + /** The intersection of this flag set and the given flag set */ + def & (that: FlagSet) = FlagSet(bits & that.bits) + + /** This flag set with all flags transposed to be type flags */ + def toTypeFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TYPES) + + /** This flag set with all flags transposed to be term flags */ + def toTermFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TERMS) + + /** This flag set with all flags transposed to be common flags */ + def toCommonFlags = if (bits == 0) this else FlagSet(bits | KINDFLAGS) + + /** The number of non-kind flags in this set */ + def numFlags: Int = java.lang.Long.bitCount(bits & ~KINDFLAGS) + } + + /** A class representing flag sets that should be tested + * conjunctively. I.e. for a flag conjunction `fc`, + * `x is fc` tests whether `x` contains all flags in `fc`. + */ + case class FlagConjunction(bits: Long) { + override def toString = FlagSet(bits).toString + } + + private final val TYPESHIFT = 2 + private final val TERMindex = 0 + private final val TYPEindex = 1 + private final val TERMS = 1 << TERMindex + private final val TYPES = 1 << TYPEindex + private final val KINDFLAGS = TERMS | TYPES + + private final val FirstFlag = 2 + private final val FirstNotPickledFlag = 48 + private final val MaxFlag = 63 + + private val flagName = Array.fill(64, 2)("") + + private def isDefinedAsFlag(idx: Int) = flagName(idx) exists (_.nonEmpty) + + /** The flag set containing all defined flags of either kind whose bits + * lie in the given range + */ + private def flagRange(start: Int, end: Int) = + FlagSet((KINDFLAGS.toLong /: (start until end)) ((bits, idx) => + if (isDefinedAsFlag(idx)) bits | (1L << idx) else bits)) + + /** The flag with given index between 2 and 63 which applies to terms. + * Installs given name as the name of the flag. */ + private def termFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + FlagSet(TERMS | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to types. + * Installs given name as the name of the flag. */ + private def typeFlag(index: Int, name: String): FlagSet = { + flagName(index)(TYPEindex) = name + FlagSet(TYPES | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to both terms and types + * Installs given name as the name of the flag. */ + private def commonFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + flagName(index)(TYPEindex) = name + FlagSet(TERMS | TYPES | (1L << index)) + } + + /** The union of all flags in given flag set */ + def union(flagss: FlagSet*): FlagSet = { + var flag = EmptyFlags + for (f <- flagss) + flag |= f + flag + } + + def commonFlags(flagss: FlagSet*) = union(flagss.map(_.toCommonFlags): _*) + + /** The empty flag set */ + final val EmptyFlags = FlagSet(0) + + /** The undefined flag set */ + final val UndefinedFlags = FlagSet(~KINDFLAGS) + + // Available flags: + + /** Labeled with `private` modifier */ + final val Private = commonFlag(2, "private") + final val PrivateTerm = Private.toTermFlags + final val PrivateType = Private.toTypeFlags + + /** Labeled with `protected` modifier */ + final val Protected = commonFlag(3, "protected") + + /** Labeled with `override` modifier */ + final val Override = commonFlag(4, "override") + + /** A declared, but not defined member */ + final val Deferred = commonFlag(5, "") + final val DeferredTerm = Deferred.toTermFlags + final val DeferredType = Deferred.toTypeFlags + + /** Labeled with `final` modifier */ + final val Final = commonFlag(6, "final") + + /** A method symbol. */ + final val Method = termFlag(7, "") + final val HigherKinded = typeFlag(7, "") + + /** A (term or type) parameter to a class or method */ + final val Param = commonFlag(8, "") + final val TermParam = Param.toTermFlags + final val TypeParam = Param.toTypeFlags + + /** Labeled with `implicit` modifier (implicit value) */ + final val ImplicitCommon = commonFlag(9, "implicit") + final val Implicit = ImplicitCommon.toTermFlags + + /** Labeled with `lazy` (a lazy val). */ + final val Lazy = termFlag(10, "lazy") + + /** A trait */ + final val Trait = typeFlag(10, "") + + final val LazyOrTrait = Lazy.toCommonFlags + + /** A value or variable accessor (getter or setter) */ + final val Accessor = termFlag(11, "") + + /** Labeled with `sealed` modifier (sealed class) */ + final val Sealed = typeFlag(11, "sealed") + + final val AccessorOrSealed = Accessor.toCommonFlags + + /** A mutable var */ + final val Mutable = termFlag(12, "mutable") + + /** Symbol is local to current class (i.e. private[this] or protected[this] + * pre: Private or Protected are also set + */ + final val Local = commonFlag(13, "") + + /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), + * or an accessor of such a field. + */ + final val ParamAccessor = termFlag(14, "") + + /** A value or class implementing a module */ + final val Module = commonFlag(15, "module") + final val ModuleVal = Module.toTermFlags + final val ModuleClass = Module.toTypeFlags + + /** A value or class representing a package */ + final val Package = commonFlag(16, "") + final val PackageVal = Package.toTermFlags + final val PackageClass = Package.toTypeFlags + + /** A case class or its companion object */ + final val Case = commonFlag(17, "case") + final val CaseClass = Case.toTypeFlags + final val CaseVal = Case.toTermFlags + + /** A compiler-generated symbol, which is visible for type-checking + * (compare with artifact) + */ + final val Synthetic = commonFlag(18, "") + + /** Labelled with `inline` modifier */ + final val Inline = commonFlag(19, "inline") + + /** A covariant type variable / an outer accessor */ + final val CovariantOrOuter = commonFlag(20, "") + final val Covariant = typeFlag(20, "") + final val OuterAccessor = termFlag(20, "") + + /** A contravariant type variable / a label method */ + final val ContravariantOrLabel = commonFlag(21, "") + final val Contravariant = typeFlag(21, "") + final val Label = termFlag(21, "