Skip to content

Commit 5855198

Browse files
committed
Avoid forcing ctors & parents which caused cycles
[Cherry-picked 987235e][modified]
1 parent 6f1932c commit 5855198

19 files changed

+177
-26
lines changed

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,24 @@ object desugar {
8585
override def ensureCompletions(using Context): Unit = {
8686
def completeConstructor(sym: Symbol) =
8787
sym.infoOrCompleter match {
88-
case completer: Namer#ClassCompleter =>
88+
case completer: Namer#ClassCompleter if !sym.isCompleting =>
89+
// An example, derived from tests/run/t6385.scala
90+
//
91+
// class Test():
92+
// def t1: Foo = Foo(1)
93+
// final case class Foo(value: Int)
94+
//
95+
// Here's the sequence of events:
96+
// * The symbol for Foo.apply is forced to complete
97+
// * The symbol for the `value` parameter of the apply method is forced to complete
98+
// * Completing that value parameter requires typing its type, which is a DerivedTypeTrees,
99+
// which only types if it has an OriginalSymbol.
100+
// * So if the case class hasn't been completed, we need (at least) its constructor to be completed
101+
//
102+
// Test tests/neg/i9294.scala is an example of why isCompleting is necessary.
103+
// Annotations are added while completing the constructor,
104+
// so the back reference to foo reaches here which re-initiates the constructor completion.
105+
// So we just skip, as completion is already being triggered.
89106
completer.completeConstructor(sym)
90107
case _ =>
91108
}

compiler/src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ class TypeApplications(val self: Type) extends AnyVal {
256256
*/
257257
def hkResult(using Context): Type = self.dealias match {
258258
case self: TypeRef =>
259-
if (self.symbol == defn.AnyKindClass) self else self.info.hkResult
259+
if self.symbol == defn.AnyKindClass then self
260+
else if self.symbol.isClass then NoType // avoid forcing symbol if it's a class, not an alias to a HK type lambda
261+
else self.info.hkResult
260262
case self: AppliedType =>
261263
if (self.tycon.typeSymbol.isClass) NoType else self.superType.hkResult
262264
case self: HKTypeLambda => self.resultType

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ object Types extends TypeUtils {
195195
*/
196196
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = this match {
197197
case this1: TypeRef =>
198-
this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
198+
// avoid forcing symbol if it's a class, not a type alias (see i15177.FakeEnum.scala)
199+
if this1.symbol.isClass then this1.symbol eq sym
200+
else this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
199201
case TypeAlias(tp) => tp.isRef(sym, skipRefined)
200202
case _ => this1.symbol eq sym
201203
}

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -800,8 +800,11 @@ class Namer { typer: Typer =>
800800
if (sym.is(Module)) moduleValSig(sym)
801801
else valOrDefDefSig(original, sym, Nil, identity)(using localContext(sym).setNewScope)
802802
case original: DefDef =>
803-
val typer1 = ctx.typer.newLikeThis(ctx.nestingLevel + 1)
804-
nestedTyper(sym) = typer1
803+
// For the primary constructor DefDef, it is:
804+
// * indexed as a part of completing the class, with indexConstructor; and
805+
// * typed ahead when completing the constructor
806+
// So we need to make sure to reuse the same local/nested typer.
807+
val typer1 = nestedTyper.getOrElseUpdate(sym, ctx.typer.newLikeThis(ctx.nestingLevel + 1))
805808
typer1.defDefSig(original, sym, this)(using localContext(sym).setTyper(typer1))
806809
case imp: Import =>
807810
try
@@ -811,6 +814,12 @@ class Namer { typer: Typer =>
811814
typr.println(s"error while completing ${imp.expr}")
812815
throw ex
813816

817+
/** Context setup for indexing the constructor. */
818+
def indexConstructor(constr: DefDef, sym: Symbol): Unit =
819+
val typer1 = ctx.typer.newLikeThis(ctx.nestingLevel + 1)
820+
nestedTyper(sym) = typer1
821+
typer1.indexConstructor(constr, sym)(using localContext(sym).setTyper(typer1))
822+
814823
final override def complete(denot: SymDenotation)(using Context): Unit = {
815824
if (Config.showCompletions && ctx.typerState != creationContext.typerState) {
816825
def levels(c: Context): Int =
@@ -966,15 +975,19 @@ class Namer { typer: Typer =>
966975

967976
/** If completion of the owner of the to be completed symbol has not yet started,
968977
* complete the owner first and check again. This prevents cyclic references
969-
* where we need to copmplete a type parameter that has an owner that is not
978+
* where we need to complete a type parameter that has an owner that is not
970979
* yet completed. Test case is pos/i10967.scala.
971980
*/
972981
override def needsCompletion(symd: SymDenotation)(using Context): Boolean =
973982
val owner = symd.owner
974983
!owner.exists
975984
|| owner.is(Touched)
976985
|| {
977-
owner.ensureCompleted()
986+
// Only complete the owner if it's a type (eg. the class that owns a type parameter)
987+
// This avoids completing primary constructor methods while completing the type of one of its type parameters
988+
// See i15177.scala.
989+
if owner.isType then
990+
owner.ensureCompleted()
978991
!symd.isCompleted
979992
}
980993

@@ -1498,7 +1511,10 @@ class Namer { typer: Typer =>
14981511
index(constr)
14991512
index(rest)(using localCtx)
15001513

1501-
checkCaseClassParamDependencies(symbolOfTree(constr).info, cls) // Completes constr symbol as a side effect
1514+
val constrSym = symbolOfTree(constr)
1515+
constrSym.infoOrCompleter match
1516+
case completer: Completer => completer.indexConstructor(constr, constrSym)
1517+
case _ =>
15021518

15031519
tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo]
15041520
denot.info = savedInfo
@@ -1705,11 +1721,14 @@ class Namer { typer: Typer =>
17051721
val sym = tree.symbol
17061722
if sym.isConstructor then sym.owner else sym
17071723

1708-
/** Enter and typecheck parameter list */
1709-
def completeParams(params: List[MemberDef])(using Context): Unit = {
1710-
index(params)
1711-
for (param <- params) typedAheadExpr(param)
1712-
}
1724+
/** Index the primary constructor of a class, as a part of completing that class.
1725+
* This allows the rest of the constructor completion to be deferred,
1726+
* which avoids non-cyclic classes failing, e.g. pos/i15177.
1727+
*/
1728+
def indexConstructor(constr: DefDef, sym: Symbol)(using Context): Unit =
1729+
index(constr.leadingTypeParams)
1730+
sym.owner.typeParams.foreach(_.ensureCompleted())
1731+
completeTrailingParamss(constr, sym, indexingCtor = true)
17131732

17141733
/** The signature of a module valdef.
17151734
* This will compute the corresponding module class TypeRef immediately
@@ -1813,13 +1832,13 @@ class Namer { typer: Typer =>
18131832
// 3. Info of CP is computed (to be copied to DP).
18141833
// 4. CP is completed.
18151834
// 5. Info of CP is copied to DP and DP is completed.
1816-
index(ddef.leadingTypeParams)
1817-
if (isConstructor) sym.owner.typeParams.foreach(_.ensureCompleted())
1835+
if !sym.isPrimaryConstructor then
1836+
index(ddef.leadingTypeParams)
18181837
val completedTypeParams =
18191838
for tparam <- ddef.leadingTypeParams yield typedAheadExpr(tparam).symbol
18201839
if completedTypeParams.forall(_.isType) then
18211840
completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]])
1822-
completeTrailingParamss(ddef, sym)
1841+
completeTrailingParamss(ddef, sym, indexingCtor = false)
18231842
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor)
18241843
sym.setParamss(paramSymss)
18251844

@@ -1829,20 +1848,31 @@ class Namer { typer: Typer =>
18291848
if isConstructor then
18301849
// set result type tree to unit, but take the current class as result type of the symbol
18311850
typedAheadType(ddef.tpt, defn.UnitType)
1832-
wrapMethType(effectiveResultType(sym, paramSymss))
1851+
val mt = wrapMethType(effectiveResultType(sym, paramSymss))
1852+
if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner)
1853+
mt
18331854
else
18341855
valOrDefDefSig(ddef, sym, paramSymss, wrapMethType)
18351856
}
18361857

1837-
def completeTrailingParamss(ddef: DefDef, sym: Symbol)(using Context): Unit =
1858+
/** Complete the trailing parameters of a DefDef,
1859+
* as a part of indexing the primary constructor or
1860+
* as a part of completing a DefDef, including the primary constructor.
1861+
*/
1862+
def completeTrailingParamss(ddef: DefDef, sym: Symbol, indexingCtor: Boolean)(using Context): Unit =
18381863
/** Enter and typecheck parameter list.
18391864
* Once all witness parameters for a context bound are seen, create a
18401865
* context bound companion for it.
18411866
*/
18421867
def completeParams(params: List[MemberDef])(using Context): Unit =
1843-
index(params)
1868+
if indexingCtor || !sym.isPrimaryConstructor then
1869+
index(params)
1870+
var prevParams = Set.empty[Name]
18441871
for param <- params do
1845-
typedAheadExpr(param)
1872+
if !indexingCtor then
1873+
typedAheadExpr(param)
1874+
1875+
prevParams += param.name
18461876

18471877
ddef.trailingParamss.foreach(completeParams)
18481878
end completeTrailingParamss

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,12 +2221,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22212221
(arg, tparamBounds)
22222222
else
22232223
(arg, WildcardType)
2224-
if (tpt1.symbol.isClass)
2225-
tparam match {
2226-
case tparam: Symbol =>
2227-
tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work
2228-
case _ =>
2229-
}
22302224
if (desugaredArg.isType)
22312225
arg match {
22322226
case untpd.WildcardTypeBoundsTree()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Like tests/neg/i15177.FakeEnum.min.scala
2+
// But with an actual upper-bound requirement
3+
// Which shouldn't be ignored as a part of overcoming the the cycle
4+
trait Foo
5+
trait X[T <: Foo] { trait Id }
6+
object A extends X[B] // error: Type argument B does not conform to upper bound Foo
7+
class B extends A.Id

tests/neg/i15177.constr-dep.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// An example of how constructor _type_ parameters
2+
// Which can _not_ be passed to the extends part
3+
// That makes it part of the parent type,
4+
// which has been found to be unsound.
5+
class Foo[A]
6+
class Foo1(val x: Int)
7+
extends Foo[ // error: The type of a class parent cannot refer to constructor parameters, but Foo[(Foo1.this.x : Int)] refers to x
8+
x.type
9+
]

tests/neg/i15177.ub.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// like tests/pos/i15177.scala
2+
// but with T having an upper bound
3+
// that B doesn't conform to
4+
// just to be sure that not forcing B
5+
// doesn't backdoor an illegal X[B]
6+
class X[T <: C] {
7+
type Id
8+
}
9+
object A
10+
extends X[ // error
11+
B] // error
12+
class B(id: A.Id)
13+
class C
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Like tests/neg/i15177.FakeEnum.min.scala
2+
// With an actual upper-bound requirement
3+
// But that is satisfied on class B
4+
trait Foo
5+
trait X[T <: Foo] { trait Id }
6+
object A extends X[B]
7+
class B extends A.Id with Foo
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Like tests/neg/i15177.FakeEnum.min.scala
2+
// With an actual upper-bound requirement
3+
// But that is satisfied on trait Id
4+
trait Foo
5+
trait X[T <: Foo] { trait Id extends Foo }
6+
object A extends X[B]
7+
class B extends A.Id

tests/pos/i15177.FakeEnum.min.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Minimisation of tests/neg/i15177.FakeEnum.scala
2+
trait X[T] { trait Id }
3+
object A extends X[B]
4+
class B extends A.Id

tests/pos/i15177.FakeEnum.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// From https://github.com/scala/scala3/issues/15177#issuecomment-1463088400
2+
trait FakeEnum[A, @specialized(Byte, Short, Int, Long) B]
3+
{
4+
trait Value {
5+
self: A =>
6+
def name: String
7+
def id: B
8+
}
9+
}
10+
11+
object FakeEnumType
12+
extends FakeEnum[FakeEnumType, Short]
13+
{
14+
val MEMBER1 = new FakeEnumType((0: Short), "MEMBER1") {}
15+
val MEMBER2 = new FakeEnumType((1: Short), "MEMBER2") {}
16+
}
17+
18+
sealed abstract
19+
class FakeEnumType(val id: Short, val name: String)
20+
extends FakeEnumType.Value
21+
{}

tests/pos/i15177.app.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// like tests/pos/i15177.scala
2+
// but with an applied type B[D]
3+
class X[T] { type Id }
4+
object A extends X[B[D]]
5+
class B[C](id: A.Id)
6+
class D

tests/pos/i15177.constr-dep.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// An example of how constructor _term_ parameters
2+
// Can be passed to the extends part
3+
// But that doesn't mean the parent type,
4+
// it's just the super constructor call.
5+
class Bar(val y: Long)
6+
class Bar1(val z: Long) extends Bar(z)

tests/pos/i15177.hk.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// like tests/pos/i15177.scala
2+
// but with B being higher kinded
3+
class X[T[_]] { type Id }
4+
object A extends X[B]
5+
class B[C](id: A.Id)

tests/pos/i15177.hk2.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// like tests/pos/i15177.scala
2+
// but with B being higher kinded
3+
// but without the actual cycle (like .without)
4+
class X[T[_]] { type Id }
5+
class A extends X[B]
6+
class B[C]

tests/pos/i15177.orig.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
trait DomainIdProvider[T] {
2+
type Id = List[T]
3+
}
4+
object Country extends DomainIdProvider[Country]
5+
case class Country(
6+
id: Country.Id,
7+
)

tests/pos/i15177.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class X[T] { trait Id }
2+
object A extends X[B]
3+
class B(id: A.Id)

tests/pos/i15177.without.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// like tests/pos/i15177.scala
2+
// but without the actual cycle
3+
class X[T] { trait Id }
4+
class A extends X[B]
5+
class B

0 commit comments

Comments
 (0)