Skip to content

Commit 32cb403

Browse files
committed
Adjust purity of trait with lazy member
1 parent 4493b49 commit 32cb403

File tree

9 files changed

+73
-19
lines changed

9 files changed

+73
-19
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -483,9 +483,11 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
483483
*/
484484
private def defKind(tree: Tree)(using Context): FlagSet = unsplice(tree) match {
485485
case EmptyTree | _: Import => NoInitsInterface
486-
case tree: TypeDef if Feature.shouldBehaveAsScala2 =>
487-
if (tree.isClassDef) EmptyFlags else NoInitsInterface
488-
case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface
486+
case tree: TypeDef =>
487+
if tree.isClassDef then
488+
if Feature.shouldBehaveAsScala2 then EmptyFlags
489+
else NoInits
490+
else NoInitsInterface
489491
case tree: DefDef =>
490492
if tree.unforcedRhs == EmptyTree
491493
&& tree.paramss.forall {
@@ -494,8 +496,6 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
494496
}
495497
then
496498
NoInitsInterface
497-
else if tree.mods.is(Given) && tree.paramss.isEmpty then
498-
EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl
499499
else if Feature.shouldBehaveAsScala2 then
500500
EmptyFlags
501501
else

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,8 +813,8 @@ class TreeUnpickler(reader: TastyReader,
813813
if (sym.isTerm && !sym.isOneOf(DeferredOrLazyOrMethod))
814814
initsFlags = EmptyFlags
815815
else if (sym.isClass ||
816-
sym.is(Method, butNot = Deferred) && !sym.isConstructor)
817-
initsFlags &= NoInits
816+
sym.isOneOf(Lazy | Method, butNot = Deferred) && !sym.isConstructor)
817+
initsFlags &= NoInits // i.e. initsFlags &~= PureInterface
818818
case IMPORT | EXPORT =>
819819
skipTree()
820820
case PACKAGE =>

compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dotc
33
package transform
44

55
import MegaPhase.*
6+
import ast.tpd.*
67
import core.DenotTransformers.*
78
import core.Symbols.*
89
import core.Contexts.*
@@ -15,10 +16,8 @@ import core.Names.*
1516
import core.NameOps.*
1617
import core.NameKinds.SuperArgName
1718

18-
import dotty.tools.dotc.ast.tpd
19-
20-
import collection.mutable
2119
import scala.annotation.tailrec
20+
import scala.collection.mutable
2221

2322
/** This phase adds outer accessors to classes and traits that need them.
2423
* Compared to Scala 2.x, it tries to minimize the set of classes
@@ -36,7 +35,6 @@ import scala.annotation.tailrec
3635
*/
3736
class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase =>
3837
import ExplicitOuter.*
39-
import ast.tpd.*
4038

4139
override def phaseName: String = ExplicitOuter.name
4240

@@ -64,7 +62,7 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase =>
6462
* Furthermore, if a parent trait might have an outer accessor,
6563
* provide an implementation for the outer accessor by computing the parent's
6664
* outer from the parent type prefix. If the trait ends up not having an outer accessor
67-
* after all, the implementation is redundant, but does not harm.
65+
* after all, the implementation is redundant, but does no harm.
6866
* The same logic is not done for non-trait parent classes because for them the outer
6967
* pointer is passed in the super constructor, which will be implemented later in
7068
* a separate phase which needs to run after erasure. However, we make sure here
@@ -111,7 +109,7 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase =>
111109
else impl
112110
}
113111

114-
override def transformClosure(tree: Closure)(using Context): tpd.Tree = {
112+
override def transformClosure(tree: Closure)(using Context): Tree = {
115113
if (tree.tpt ne EmptyTree) {
116114
val cls = tree.tpt.asInstanceOf[TypeTree].tpe.classSymbol
117115
if (cls.exists && hasOuter(cls.asClass))
@@ -122,7 +120,6 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase =>
122120
}
123121

124122
object ExplicitOuter {
125-
import ast.tpd.*
126123

127124
val name: String = "explicitOuter"
128125
val description: String = "add accessors to outer classes from nested ones"
@@ -217,11 +214,12 @@ object ExplicitOuter {
217214
* - we need to potentially pass along outer to a parent class or trait
218215
*/
219216
private def needsOuterAlways(cls: ClassSymbol)(using Context): Boolean =
220-
needsOuterIfReferenced(cls) &&
221-
(!hasOnlyLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not
222-
cls.mixins.exists(needsOuterIfReferenced) || // needs outer for parent traits
223-
cls.info.parents.exists(parent => // needs outer to potentially pass along to parent
224-
needsOuterIfReferenced(parent.classSymbol.asClass)))
217+
needsOuterIfReferenced(cls)
218+
&& (!hasOnlyLocalInstantiation(cls) // needs outer because we might not know whether outer is referenced or not
219+
|| cls.mixins.exists(needsOuterIfReferenced) // needs outer for parent traits
220+
|| cls.info.parents.exists: parent => // needs outer to potentially pass along to parent
221+
needsOuterIfReferenced(parent.classSymbol.asClass)
222+
)
225223

226224
/** Class is only instantiated in the compilation unit where it is defined */
227225
private def hasOnlyLocalInstantiation(cls: ClassSymbol)(using Context): Boolean =

tests/run/i23245a/api.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
package logadapter:
3+
4+
trait AbstractLogAdapter:
5+
def info(message: String): Unit
6+
7+
trait AbstractApi[T <: AbstractLogAdapter]:
8+
def logAdapterFor(loggerName: String): T
9+
trait SelfLogging:
10+
given adapter: T = logAdapterFor(this.getClass.getName)
11+
// workaround:
12+
//given () => T = logAdapterFor(this.getClass.getName)
13+
// or
14+
//private val adapter = logAdapterFor(this.getClass.getName)
15+
//given T = adapter
16+
// or just pollute the interface so it's never taken as pure
17+
//private val z = 42
18+
19+
object Api extends AbstractApi[LogAdapter]:
20+
def logAdapterFor(loggerName: String): LogAdapter = new LogAdapter(loggerName)
21+
22+
class LogAdapter(loggerName: String) extends AbstractLogAdapter:
23+
def info(message: String): Unit = System.err.println(s"INFO [${loggerName}] ${message}")

tests/run/i23245a/test_2.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
3+
object Test extends logadapter.Api.SelfLogging:
4+
def main(args: Array[String]): Unit =
5+
summon[logadapter.LogAdapter].info("Hello")
6+

tests/run/i23245b/outer.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
trait T:
3+
def f = 42
4+
trait D:
5+
lazy val g = f
6+
7+
object C extends T
8+
9+
// D parent of Z is taken as PureInterface under separate compilation
10+
// and thus doesn't get an outer.

tests/run/i23245b/test_2.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
object Z extends C.D
3+
4+
@main def Test = println:
5+
Z.g

tests/run/i23245c/outer.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
trait T:
3+
def f = 42
4+
trait D:
5+
lazy val g = f
6+
7+
object C extends T

tests/run/i23245c/test.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
object Z extends C.D
3+
4+
@main def Test = println:
5+
Z.g

0 commit comments

Comments
 (0)