From 585fd544be6a3cc3bed8e6190189e715e46290e2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Jul 2019 22:58:45 +0200 Subject: [PATCH 01/10] Don't exit with StaleSymbol errors when testing pickling. Stale symbol errors can happen when inspecting symbols while comparing the trees before and after pickling. --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 7c62a812595c..373fa83b1c96 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -102,8 +102,12 @@ trait SymDenotations { this: Context => } } - /** Configurable: Accept stale symbol with warning if in IDE */ - def staleOK: Boolean = Config.ignoreStaleInIDE && mode.is(Mode.Interactive) + /** Configurable: Accept stale symbol with warning if in IDE + * Always accept stale symbols when testing pickling. + */ + def staleOK: Boolean = + Config.ignoreStaleInIDE && mode.is(Mode.Interactive) || + settings.YtestPickler.value /** Possibly accept stale symbol with warning if in IDE */ def acceptStale(denot: SingleDenotation): Boolean = From c19ff89da45fc3d2fe24f2556abfd16b13c79850 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 30 Jul 2019 16:16:41 +0200 Subject: [PATCH 02/10] Make entered and enteredAfter also work for term members Restricting `entered` and `enteredAfter` to class members is more a trap to fall into than a helpful check. --- .../src/dotty/tools/dotc/core/Symbols.scala | 25 +++++++++++-------- .../dotc/transform/CacheAliasImplicits.scala | 2 +- .../tools/dotc/transform/HoistSuperArgs.scala | 6 ++--- .../tools/dotc/transform/LambdaLift.scala | 5 ++-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 24d2c188a419..3aaf8cb80838 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -553,9 +553,10 @@ object Symbols { /** This symbol entered into owner's scope (owner must be a class). */ final def entered(implicit ctx: Context): this.type = { - assert(this.owner.isClass, s"symbol ($this) entered the scope of non-class owner ${this.owner}") // !!! DEBUG - this.owner.asClass.enter(this) - if (this.is(Module)) this.owner.asClass.enter(this.moduleClass) + if (this.owner.isClass) { + this.owner.asClass.enter(this) + if (this.is(Module)) this.owner.asClass.enter(this.moduleClass) + } this } @@ -566,14 +567,16 @@ object Symbols { */ def enteredAfter(phase: DenotTransformer)(implicit ctx: Context): this.type = if (ctx.phaseId != phase.next.id) enteredAfter(phase)(ctx.withPhase(phase.next)) - else { - if (this.owner.is(Package)) { - denot.validFor |= InitialPeriod - if (this.is(Module)) this.moduleClass.validFor |= InitialPeriod - } - else this.owner.asClass.ensureFreshScopeAfter(phase) - assert(isPrivate || phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase") - entered + else this.owner match { + case owner: ClassSymbol => + if (owner.is(Package)) { + denot.validFor |= InitialPeriod + if (this.is(Module)) this.moduleClass.validFor |= InitialPeriod + } + else owner.ensureFreshScopeAfter(phase) + assert(isPrivate || phase.changesMembers, i"$this entered in $owner at undeclared phase $phase") + entered + case _ => this } /** Remove symbol from scope of owning class */ diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index 29eae5107ec7..9cf5fd216388 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -82,7 +82,7 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this val cacheFlags = if (ctx.owner.isClass) Private | Local | Mutable else Mutable val cacheSym = ctx.newSymbol(ctx.owner, CacheName(tree.name), cacheFlags, rhsType, coord = sym.coord) - if (ctx.owner.isClass) cacheSym.enteredAfter(thisPhase) + .enteredAfter(thisPhase) val cacheDef = ValDef(cacheSym, tpd.defaultValue(rhsType)) val cachingDef = cpy.DefDef(tree)(rhs = Block( diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index 302d2330d056..d72f0895ca49 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -91,13 +91,13 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase val argTypeWrtConstr = argType.subst(origParams, allParamRefs(constr.info)) // argType with references to paramRefs of the primary constructor instead of // local parameter accessors - val meth = ctx.newSymbol( + ctx.newSymbol( owner = methOwner, name = SuperArgName.fresh(cls.name.toTermName), flags = Synthetic | Private | Method | staticFlag, info = replaceResult(constr.info, argTypeWrtConstr), - coord = constr.coord) - if (methOwner.isClass) meth.enteredAfter(thisPhase) else meth + coord = constr.coord + ).enteredAfter(thisPhase) } /** Type of a reference implies that it needs to be hoisted */ diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index b2a301fc7d84..ecc805746b2a 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -300,8 +300,9 @@ object LambdaLift { proxyMap(owner) = { for (fv <- freeValues.toList) yield { val proxyName = newName(fv) - val proxy = ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord) - if (owner.isClass) proxy.enteredAfter(thisPhase) + val proxy = + ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord) + .enteredAfter(thisPhase) (fv, proxy) } }.toMap From 45857ed2676ce93be49385c009c7752d9e54a2ea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 30 Jul 2019 16:29:21 +0200 Subject: [PATCH 03/10] Streamline Typer#traverse --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b25050d5af8a..892ef2a15d93 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2132,15 +2132,13 @@ class Typer extends Namer buf += inlineExpansion(mdef1) // replace body with expansion, because it will be used as inlined body // from separately compiled files - the original BodyAnnotation is not kept. + case mdef1: TypeDef if mdef1.symbol.is(Enum, butNot = Case) => + enumContexts(mdef1.symbol) = ctx + buf += mdef1 + case EmptyTree => + // clashing synthetic case methods are converted to empty trees, drop them here case mdef1 => - import untpd.modsDeco - mdef match { - case mdef: untpd.TypeDef if mdef.mods.isEnumClass => - enumContexts(mdef1.symbol) = ctx - case _ => - } - if (!mdef1.isEmpty) // clashing synthetic case methods are converted to empty trees - buf += mdef1 + buf += mdef1 } traverse(rest) } From a5eaa0975ab34e105fec73090ac6c90191605d21 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 13:16:47 +0200 Subject: [PATCH 04/10] Cache alias implicits with lazy vals --- .../dotc/transform/CacheAliasImplicits.scala | 74 ++++++------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index 9cf5fd216388..f9207e1c0588 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -27,25 +27,18 @@ object CacheAliasImplicits { * is cached. It applies to all alias implicits that have neither type parameters * nor a given clause. Example: The alias * - * implicit a for TC = rhs + * given a as TC = rhs * - * is expanded before this phase + * is expanded before this phase to: * * implicit def a: TC = rhs * * It is then expanded further as follows: * - * 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix), leave the definition as is. - * 2. Otherwise, if `rhs` is a pure path, replace the definition with + * 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix) that + * refers to a value, leave it as is. * - * implicit val a: TC = rhs - * - * 3. Otherwise, if `TC` is a reference type, replace the definition with - * - * private[this] var a$_cache: TC = null - * implicit def a: TC = { if (a$_cache == null) a$_cache = rhs; a$_cache } - * - * 4. Otherwise `TC` is a value type. Replace the definition with + * 2. Otherwise, replace the definition with * * lazy implicit val a: TC = rhs */ @@ -56,47 +49,26 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { val sym = tree.symbol - sym.info match { - case ExprType(rhsType) if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => - // If rhs is a simple TermRef, leave a def. - tree.rhs.tpe match { - case TermRef(pre, _) => - pre match { - case NoPrefix => return tree - case pre: ThisType if pre.cls == ctx.owner.enclosingClass => return tree - case _ => - } - case _ => + val rhsType = tree.rhs.tpe + val isCached = sym.info match { + case _: ExprType if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => + rhsType match { + case TermRef(NoPrefix, _) + if rhsType.isStable => false + case TermRef(pre: ThisType, _) + if rhsType.isStable && pre.cls == sym.owner.enclosingClass => false + case _ => true } - def makeVal(additionalFlags: FlagSet) = { - sym.copySymDenotation( - initFlags = sym.flags &~ Method | additionalFlags, - info = rhsType) - .installAfter(thisPhase) - cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs) - } - if (isPurePath(tree.rhs)) makeVal(EmptyFlags) - else if (rhsType.classSymbol.isValueClass || - !erasure(rhsType).typeSymbol.derivesFrom(defn.ObjectClass)) makeVal(Lazy) - else { - val cacheFlags = if (ctx.owner.isClass) Private | Local | Mutable else Mutable - val cacheSym = - ctx.newSymbol(ctx.owner, CacheName(tree.name), cacheFlags, rhsType, coord = sym.coord) - .enteredAfter(thisPhase) - val cacheDef = ValDef(cacheSym, tpd.defaultValue(rhsType)) - val cachingDef = cpy.DefDef(tree)(rhs = - Block( - If( - ref(cacheSym).select(defn.Any_==).appliedTo(nullLiteral), - Assign(ref(cacheSym), tree.rhs), - unitLiteral) :: Nil, - ref(cacheSym) - ) - ) - Thicket(cacheDef, cachingDef) - } - case _ => tree + case _ => false + } + if (isCached) { + sym.copySymDenotation( + initFlags = sym.flags &~ Method | Lazy, + info = rhsType) + .installAfter(thisPhase) + cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs) } + else tree } } From e02cf3ed3830cb1a668e2007d5bc02b100d46828 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 13:29:25 +0200 Subject: [PATCH 05/10] Docs and tests The new implementation does not optimize given aliases with pure paths as right hand sides to be vals anymore. The reason is that even a pure path might be expensive to compute so we might want the caching. If we want a precomputed val, we can always for this with an explicit value definition: ``` val x = a.b.c given as T = x ``` --- .../contextual/relationship-implicits.md | 19 ++++--------------- tests/pos/i6909.scala | 7 +++++++ 2 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 tests/pos/i6909.scala diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index 95b1f160ed0f..396da6d8c248 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -28,32 +28,21 @@ Given instances can be mapped to combinations of implicit objects, classes and i class ListOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... } final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T] ``` - 3. Alias givens map to implicit methods. If an alias has neither type parameters nor a given clause, its right-hand side is cached in a variable. There are two cases that can be optimized: - - - If the right hand side is a simple reference, we can - use a forwarder to that reference without caching it. - - If the right hand side is more complex, but still known to be pure, we can - create a `val` that computes it ahead of time. + 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type parameters nor a given clause, + it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to that + reference without caching it. Examples: ```scala given global as ExecutionContext = new ForkJoinContext() - given config as Config = default.config val ctx: Context given as Context = ctx ``` would map to ```scala - private[this] var global$cache: ExecutionContext | Null = null - final implicit def global: ExecutionContext = { - if (global$cache == null) global$cache = new ForkJoinContext() - global$cache - } - - final implicit val config: Config = default.config - + final implicit lazy val global: ExecutionContext = new ForkJoinContext() final implicit def Context_given = ctx ``` diff --git a/tests/pos/i6909.scala b/tests/pos/i6909.scala new file mode 100644 index 000000000000..7bbfbe50d1f3 --- /dev/null +++ b/tests/pos/i6909.scala @@ -0,0 +1,7 @@ +import scala.compiletime +trait Foo[A] + + +trait Qux { + given as Foo[Int] = new Foo[Int] {} +} \ No newline at end of file From 449474542369f6b8afe44ee4fe922529af0eeb60 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 16:48:15 +0200 Subject: [PATCH 06/10] Don't cache inline givens It's not necessary anyway, and changes the meaning of `inline`. --- .../dotc/transform/CacheAliasImplicits.scala | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index f9207e1c0588..a847fb3acad4 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -50,16 +50,18 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { val sym = tree.symbol val rhsType = tree.rhs.tpe - val isCached = sym.info match { - case _: ExprType if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => - rhsType match { - case TermRef(NoPrefix, _) - if rhsType.isStable => false - case TermRef(pre: ThisType, _) - if rhsType.isStable && pre.cls == sym.owner.enclosingClass => false - case _ => true - } - case _ => false + val isCached = !sym.is(Inline) && { + sym.info match { + case _: ExprType if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => + rhsType match { + case TermRef(NoPrefix, _) + if rhsType.isStable => false + case TermRef(pre: ThisType, _) + if rhsType.isStable && pre.cls == sym.owner.enclosingClass => false + case _ => true + } + case _ => false + } } if (isCached) { sym.copySymDenotation( From b65b5c4e07cea3de29635112b57270873d64dd29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 18:42:36 +0200 Subject: [PATCH 07/10] Fix type of lazy vals that cache alias givens. --- .../dotc/transform/CacheAliasImplicits.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index a847fb3acad4..e5e7fbc7c29b 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -49,15 +49,14 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { val sym = tree.symbol - val rhsType = tree.rhs.tpe - val isCached = !sym.is(Inline) && { + val isCached = sym.is(Inline) && { sym.info match { - case _: ExprType if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => - rhsType match { - case TermRef(NoPrefix, _) - if rhsType.isStable => false - case TermRef(pre: ThisType, _) - if rhsType.isStable && pre.cls == sym.owner.enclosingClass => false + case ExprType(resTpe) if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => + tree.rhs.tpe match { + case rhsTpe @ TermRef(NoPrefix, _) + if rhsTpe.isStable => false + case rhsTpe @ TermRef(pre: ThisType, _) + if rhsTpe.isStable && pre.cls == sym.owner.enclosingClass => false case _ => true } case _ => false @@ -66,7 +65,7 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this if (isCached) { sym.copySymDenotation( initFlags = sym.flags &~ Method | Lazy, - info = rhsType) + info = sym.info.widenExpr) .installAfter(thisPhase) cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs) } From 2e12f19ed6b4a762472c1ded483a5995c92d911b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 19:20:06 +0200 Subject: [PATCH 08/10] Fix typo --- .../src/dotty/tools/dotc/transform/CacheAliasImplicits.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index e5e7fbc7c29b..90e4febf0ff7 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -49,7 +49,7 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { val sym = tree.symbol - val isCached = sym.is(Inline) && { + val isCached = !sym.is(Inline) && { sym.info match { case ExprType(resTpe) if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) => tree.rhs.tpe match { From e46d9ab271b56999c6c4125e60913954fdcefb9e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Aug 2019 21:45:08 +0200 Subject: [PATCH 09/10] Drop NoInits flag for given aliases that might become lazy vals The current LazyVals implementation contains initialization code for the OFFSET value. Therefore we cannot set a NoInits flag for traits containing alias givens since these might become lay vals. If the lazy val implementation changes we might be able to revert that commit. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 42 ++++++++++--------- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index e655927c2eb4..fd683a5c1582 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -243,20 +243,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case y => y } - /** The largest subset of {NoInits, PureInterface} that a - * trait or class enclosing this statement can have as flags. - */ - def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match { - case EmptyTree | _: Import => NoInitsInterface - case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface - case tree: DefDef => - if (tree.unforcedRhs == EmptyTree && - tree.vparamss.forall(_.forall(_.rhs.isEmpty))) NoInitsInterface - else NoInits - case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags - case _ => EmptyFlags - } - /** The largest subset of {NoInits, PureInterface} that a * trait or class with these parents can have as flags. */ @@ -266,12 +252,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ :: parents1 => parentsKind(parents1) } - /** The largest subset of {NoInits, PureInterface} that a - * trait or class with this body can have as flags. - */ - def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet = - (NoInitsInterface /: body)((fs, stat) => fs & defKind(stat)) - /** Checks whether predicate `p` is true for all result parts of this expression, * where we zoom into Ifs, Matches, and Blocks. */ @@ -342,6 +322,28 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] case _ => false } + /** The largest subset of {NoInits, PureInterface} that a + * trait or class enclosing this statement can have as flags. + */ + def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match { + case EmptyTree | _: Import => NoInitsInterface + case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface + case tree: DefDef => + if (tree.unforcedRhs == EmptyTree && + tree.vparamss.forall(_.forall(_.rhs.isEmpty))) NoInitsInterface + else if (tree.mods.is(Given) && tree.tparams.isEmpty && tree.vparamss.isEmpty) + EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl + else NoInits + case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags + case _ => EmptyFlags + } + + /** The largest subset of {NoInits, PureInterface} that a + * trait or class with this body can have as flags. + */ + def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet = + (NoInitsInterface /: body)((fs, stat) => fs & defKind(stat)) + // todo: fill with other methods from TreeInfo that only apply to untpd.Tree's } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 13ac4fc40f9c..36fc755a67bc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1175,7 +1175,7 @@ class Namer { typer: Typer => if (isDerivedValueClass(cls)) cls.setFlag(Final) cls.info = avoidPrivateLeaks(cls) cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing - cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest)) + cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest)) if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable) processExports(localCtx) } From 6a22d2932da970ee4adf3b66e9b213524196f90d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Aug 2019 22:58:53 +0200 Subject: [PATCH 10/10] Improve failure diagnostic --- compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 5677d10c6dba..bc4626d115dc 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -404,7 +404,10 @@ class JSCodeGen()(implicit ctx: Context) { case EmptyTree => () case dd: DefDef => generatedMethods ++= genMethod(dd) case _ => - throw new FatalError("Illegal tree in gen of genInterface(): " + tree) + throw new FatalError( + i"""Illegal tree in gen of genInterface(): $tree + |class = $td + |in ${ctx.compilationUnit}""") } }