Skip to content

Commit ee01c47

Browse files
committed
Adjust purity of trait with lazy member
[Cherry-picked d364992][modified]
1 parent 38c58d0 commit ee01c47

File tree

9 files changed

+71
-17
lines changed

9 files changed

+71
-17
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,9 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
454454
*/
455455
private def defKind(tree: Tree)(using Context): FlagSet = unsplice(tree) match {
456456
case EmptyTree | _: Import => NoInitsInterface
457-
case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface
457+
case tree: TypeDef =>
458+
if tree.isClassDef then NoInits
459+
else NoInitsInterface
458460
case tree: DefDef =>
459461
if tree.unforcedRhs == EmptyTree
460462
&& tree.paramss.forall {
@@ -463,8 +465,6 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
463465
}
464466
then
465467
NoInitsInterface
466-
else if tree.mods.is(Given) && tree.paramss.isEmpty then
467-
EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl
468468
else
469469
NoInits
470470
case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,8 +773,8 @@ class TreeUnpickler(reader: TastyReader,
773773
if (sym.isTerm && !sym.isOneOf(DeferredOrLazyOrMethod))
774774
initsFlags = EmptyFlags
775775
else if (sym.isClass ||
776-
sym.is(Method, butNot = Deferred) && !sym.isConstructor)
777-
initsFlags &= NoInits
776+
sym.isOneOf(Lazy | Method, butNot = Deferred) && !sym.isConstructor)
777+
initsFlags &= NoInits // i.e. initsFlags &~= PureInterface
778778
case IMPORT | EXPORT =>
779779
skipTree()
780780
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)