Skip to content

Commit f253d1c

Browse files
Backport "Avoid forcing ctors & parents which caused cycles" to 3.5.2 (#21487)
Backports #17086 to the 3.5.2 branch. PR submitted by the release tooling. [skip ci]
2 parents da117c7 + ad03e41 commit f253d1c

21 files changed

+248
-74
lines changed

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

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ object NamerOps:
272272
* where
273273
*
274274
* <context-bound-companion> is the CBCompanion type created in Definitions
275-
* withnessRefK is a refence to the K'th witness.
275+
* withnessRefK is a reference to the K'th witness.
276276
*
277277
* The companion has the same access flags as the original type.
278278
*/

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,9 @@ class TypeApplications(val self: Type) extends AnyVal {
267267
*/
268268
def hkResult(using Context): Type = self.dealias match {
269269
case self: TypeRef =>
270-
if (self.symbol == defn.AnyKindClass) self else self.info.hkResult
270+
if self.symbol == defn.AnyKindClass then self
271+
else if self.symbol.isClass then NoType // avoid forcing symbol if it's a class, not an alias to a HK type lambda
272+
else self.info.hkResult
271273
case self: AppliedType =>
272274
if (self.tycon.typeSymbol.isClass) NoType else self.superType.hkResult
273275
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
@@ -197,7 +197,9 @@ object Types extends TypeUtils {
197197
*/
198198
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = this match {
199199
case this1: TypeRef =>
200-
this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
200+
// avoid forcing symbol if it's a class, not a type alias (see i15177.FakeEnum.scala)
201+
if this1.symbol.isClass then this1.symbol eq sym
202+
else this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
201203
case TypeAlias(tp) => tp.isRef(sym, skipRefined)
202204
case _ => this1.symbol eq sym
203205
}

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

Lines changed: 106 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,11 @@ class Namer { typer: Typer =>
822822
if (sym.is(Module)) moduleValSig(sym)
823823
else valOrDefDefSig(original, sym, Nil, identity)(using localContext(sym).setNewScope)
824824
case original: DefDef =>
825-
val typer1 = ctx.typer.newLikeThis(ctx.nestingLevel + 1)
826-
nestedTyper(sym) = typer1
825+
// For the primary constructor DefDef, it is:
826+
// * indexed as a part of completing the class, with indexConstructor; and
827+
// * typed ahead when completing the constructor
828+
// So we need to make sure to reuse the same local/nested typer.
829+
val typer1 = nestedTyper.getOrElseUpdate(sym, ctx.typer.newLikeThis(ctx.nestingLevel + 1))
827830
typer1.defDefSig(original, sym, this)(using localContext(sym).setTyper(typer1))
828831
case imp: Import =>
829832
try
@@ -833,6 +836,12 @@ class Namer { typer: Typer =>
833836
typr.println(s"error while completing ${imp.expr}")
834837
throw ex
835838

839+
/** Context setup for indexing the constructor. */
840+
def indexConstructor(constr: DefDef, sym: Symbol): Unit =
841+
val typer1 = ctx.typer.newLikeThis(ctx.nestingLevel + 1)
842+
nestedTyper(sym) = typer1
843+
typer1.indexConstructor(constr, sym)(using localContext(sym).setTyper(typer1))
844+
836845
final override def complete(denot: SymDenotation)(using Context): Unit = {
837846
if (Config.showCompletions && ctx.typerState != creationContext.typerState) {
838847
def levels(c: Context): Int =
@@ -988,15 +997,19 @@ class Namer { typer: Typer =>
988997

989998
/** If completion of the owner of the to be completed symbol has not yet started,
990999
* complete the owner first and check again. This prevents cyclic references
991-
* where we need to copmplete a type parameter that has an owner that is not
1000+
* where we need to complete a type parameter that has an owner that is not
9921001
* yet completed. Test case is pos/i10967.scala.
9931002
*/
9941003
override def needsCompletion(symd: SymDenotation)(using Context): Boolean =
9951004
val owner = symd.owner
9961005
!owner.exists
9971006
|| owner.is(Touched)
9981007
|| {
999-
owner.ensureCompleted()
1008+
// Only complete the owner if it's a type (eg. the class that owns a type parameter)
1009+
// This avoids completing primary constructor methods while completing the type of one of its type parameters
1010+
// See i15177.scala.
1011+
if owner.isType then
1012+
owner.ensureCompleted()
10001013
!symd.isCompleted
10011014
}
10021015

@@ -1521,12 +1534,9 @@ class Namer { typer: Typer =>
15211534
index(constr)
15221535
index(rest)(using localCtx)
15231536

1524-
symbolOfTree(constr).info.stripPoly match // Completes constr symbol as a side effect
1525-
case mt: MethodType if cls.is(Case) && mt.isParamDependent =>
1526-
// See issue #8073 for background
1527-
report.error(
1528-
em"""Implementation restriction: case classes cannot have dependencies between parameters""",
1529-
cls.srcPos)
1537+
val constrSym = symbolOfTree(constr)
1538+
constrSym.infoOrCompleter match
1539+
case completer: Completer => completer.indexConstructor(constr, constrSym)
15301540
case _ =>
15311541

15321542
tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo]
@@ -1757,6 +1767,17 @@ class Namer { typer: Typer =>
17571767
val sym = tree.symbol
17581768
if sym.isConstructor then sym.owner else sym
17591769

1770+
/** Index the primary constructor of a class, as a part of completing that class.
1771+
* This allows the rest of the constructor completion to be deferred,
1772+
* which avoids non-cyclic classes failing, e.g. pos/i15177.
1773+
*/
1774+
def indexConstructor(constr: DefDef, sym: Symbol)(using Context): Unit =
1775+
index(constr.leadingTypeParams)
1776+
sym.owner.typeParams.foreach(_.ensureCompleted())
1777+
completeTrailingParamss(constr, sym, indexingCtor = true)
1778+
if Feature.enabled(modularity) then
1779+
constr.termParamss.foreach(_.foreach(setTracked))
1780+
17601781
/** The signature of a module valdef.
17611782
* This will compute the corresponding module class TypeRef immediately
17621783
* without going through the defined type of the ValDef. This is necessary
@@ -1855,31 +1876,6 @@ class Namer { typer: Typer =>
18551876
// Beware: ddef.name need not match sym.name if sym was freshened!
18561877
val isConstructor = sym.name == nme.CONSTRUCTOR
18571878

1858-
// A map from context-bounded type parameters to associated evidence parameter names
1859-
val witnessNamesOfParam = mutable.Map[TypeDef, List[TermName]]()
1860-
if !ddef.name.is(DefaultGetterName) && !sym.is(Synthetic) then
1861-
for params <- ddef.paramss; case tdef: TypeDef <- params do
1862-
for case WitnessNamesAnnot(ws) <- tdef.mods.annotations do
1863-
witnessNamesOfParam(tdef) = ws
1864-
1865-
/** Is each name in `wnames` defined somewhere in the longest prefix of all `params`
1866-
* that have been typed ahead (i.e. that carry the TypedAhead attachment)?
1867-
*/
1868-
def allParamsSeen(wnames: List[TermName], params: List[MemberDef]) =
1869-
(wnames.toSet[Name] -- params.takeWhile(_.hasAttachment(TypedAhead)).map(_.name)).isEmpty
1870-
1871-
/** Enter and typecheck parameter list.
1872-
* Once all witness parameters for a context bound are seen, create a
1873-
* context bound companion for it.
1874-
*/
1875-
def completeParams(params: List[MemberDef])(using Context): Unit =
1876-
index(params)
1877-
for param <- params do
1878-
typedAheadExpr(param)
1879-
for (tdef, wnames) <- witnessNamesOfParam do
1880-
if wnames.contains(param.name) && allParamsSeen(wnames, params) then
1881-
addContextBoundCompanionFor(symbolOfTree(tdef), wnames, params.map(symbolOfTree))
1882-
18831879
// The following 3 lines replace what was previously just completeParams(tparams).
18841880
// But that can cause bad bounds being computed, as witnessed by
18851881
// tests/pos/paramcycle.scala. The problematic sequence is this:
@@ -1903,39 +1899,16 @@ class Namer { typer: Typer =>
19031899
// 3. Info of CP is computed (to be copied to DP).
19041900
// 4. CP is completed.
19051901
// 5. Info of CP is copied to DP and DP is completed.
1906-
index(ddef.leadingTypeParams)
1907-
if (isConstructor) sym.owner.typeParams.foreach(_.ensureCompleted())
1902+
if !sym.isPrimaryConstructor then
1903+
index(ddef.leadingTypeParams)
19081904
val completedTypeParams =
19091905
for tparam <- ddef.leadingTypeParams yield typedAheadExpr(tparam).symbol
19101906
if completedTypeParams.forall(_.isType) then
19111907
completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]])
1912-
ddef.trailingParamss.foreach(completeParams)
1908+
completeTrailingParamss(ddef, sym, indexingCtor = false)
19131909
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor)
19141910
sym.setParamss(paramSymss)
19151911

1916-
/** Under x.modularity, we add `tracked` to context bound witnesses
1917-
* that have abstract type members
1918-
*/
1919-
def needsTracked(sym: Symbol, param: ValDef)(using Context) =
1920-
!sym.is(Tracked)
1921-
&& param.hasAttachment(ContextBoundParam)
1922-
&& sym.info.memberNames(abstractTypeNameFilter).nonEmpty
1923-
1924-
/** Under x.modularity, set every context bound evidence parameter of a class to be tracked,
1925-
* provided it has a type that has an abstract type member. Reset private and local flags
1926-
* so that the parameter becomes a `val`.
1927-
*/
1928-
def setTracked(param: ValDef): Unit =
1929-
val sym = symbolOfTree(param)
1930-
sym.maybeOwner.maybeOwner.infoOrCompleter match
1931-
case info: TempClassInfo if needsTracked(sym, param) =>
1932-
typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}")
1933-
for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do
1934-
acc.resetFlag(PrivateLocal)
1935-
acc.setFlag(Tracked)
1936-
sym.setFlag(Tracked)
1937-
case _ =>
1938-
19391912
def wrapMethType(restpe: Type): Type =
19401913
instantiateDependent(restpe, paramSymss)
19411914
methodType(paramSymss, restpe, ddef.mods.is(JavaDefined))
@@ -1944,11 +1917,11 @@ class Namer { typer: Typer =>
19441917
wrapMethType(addParamRefinements(restpe, paramSymss))
19451918

19461919
if isConstructor then
1947-
if sym.isPrimaryConstructor && Feature.enabled(modularity) then
1948-
ddef.termParamss.foreach(_.foreach(setTracked))
19491920
// set result type tree to unit, but take the current class as result type of the symbol
19501921
typedAheadType(ddef.tpt, defn.UnitType)
1951-
wrapMethType(effectiveResultType(sym, paramSymss))
1922+
val mt = wrapMethType(effectiveResultType(sym, paramSymss))
1923+
if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner)
1924+
mt
19521925
else if sym.isAllOf(Given | Method) && Feature.enabled(modularity) then
19531926
// set every context bound evidence parameter of a given companion method
19541927
// to be tracked, provided it has a type that has an abstract type member.
@@ -1961,6 +1934,75 @@ class Namer { typer: Typer =>
19611934
valOrDefDefSig(ddef, sym, paramSymss, wrapMethType)
19621935
end defDefSig
19631936

1937+
/** Complete the trailing parameters of a DefDef,
1938+
* as a part of indexing the primary constructor or
1939+
* as a part of completing a DefDef, including the primary constructor.
1940+
*/
1941+
def completeTrailingParamss(ddef: DefDef, sym: Symbol, indexingCtor: Boolean)(using Context): Unit =
1942+
// A map from context-bounded type parameters to associated evidence parameter names
1943+
val witnessNamesOfParam = mutable.Map[TypeDef, List[TermName]]()
1944+
if !ddef.name.is(DefaultGetterName) && !sym.is(Synthetic) && (indexingCtor || !sym.isPrimaryConstructor) then
1945+
for params <- ddef.paramss; case tdef: TypeDef <- params do
1946+
for case WitnessNamesAnnot(ws) <- tdef.mods.annotations do
1947+
witnessNamesOfParam(tdef) = ws
1948+
1949+
/** Is each name in `wnames` defined somewhere in the previous parameters? */
1950+
def allParamsSeen(wnames: List[TermName], prevParams: Set[Name]) =
1951+
(wnames.toSet[Name] -- prevParams).isEmpty
1952+
1953+
/** Enter and typecheck parameter list.
1954+
* Once all witness parameters for a context bound are seen, create a
1955+
* context bound companion for it.
1956+
*/
1957+
def completeParams(params: List[MemberDef])(using Context): Unit =
1958+
if indexingCtor || !sym.isPrimaryConstructor then
1959+
index(params)
1960+
var prevParams = Set.empty[Name]
1961+
for param <- params do
1962+
if !indexingCtor then
1963+
typedAheadExpr(param)
1964+
1965+
prevParams += param.name
1966+
for (tdef, wnames) <- witnessNamesOfParam do
1967+
if wnames.contains(param.name) && allParamsSeen(wnames, prevParams) then
1968+
addContextBoundCompanionFor(symbolOfTree(tdef), wnames, params.map(symbolOfTree))
1969+
1970+
ddef.trailingParamss.foreach(completeParams)
1971+
end completeTrailingParamss
1972+
1973+
/** Checks an implementation restriction on case classes. */
1974+
def checkCaseClassParamDependencies(mt: Type, cls: Symbol)(using Context): Unit =
1975+
mt.stripPoly match
1976+
case mt: MethodType if cls.is(Case) && mt.isParamDependent =>
1977+
// See issue #8073 for background
1978+
report.error(
1979+
em"""Implementation restriction: case classes cannot have dependencies between parameters""",
1980+
cls.srcPos)
1981+
case _ =>
1982+
1983+
/** Under x.modularity, we add `tracked` to context bound witnesses
1984+
* that have abstract type members
1985+
*/
1986+
def needsTracked(sym: Symbol, param: ValDef)(using Context) =
1987+
!sym.is(Tracked)
1988+
&& param.hasAttachment(ContextBoundParam)
1989+
&& sym.info.memberNames(abstractTypeNameFilter).nonEmpty
1990+
1991+
/** Under x.modularity, set every context bound evidence parameter of a class to be tracked,
1992+
* provided it has a type that has an abstract type member. Reset private and local flags
1993+
* so that the parameter becomes a `val`.
1994+
*/
1995+
def setTracked(param: ValDef)(using Context): Unit =
1996+
val sym = symbolOfTree(param)
1997+
sym.maybeOwner.maybeOwner.infoOrCompleter match
1998+
case info: ClassInfo if needsTracked(sym, param) =>
1999+
typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}")
2000+
for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do
2001+
acc.resetFlag(PrivateLocal)
2002+
acc.setFlag(Tracked)
2003+
sym.setFlag(Tracked)
2004+
case _ =>
2005+
19642006
def inferredResultType(
19652007
mdef: ValOrDefDef,
19662008
sym: Symbol,

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,12 +2499,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24992499
(arg, tparamBounds)
25002500
else
25012501
(arg, WildcardType)
2502-
if (tpt1.symbol.isClass)
2503-
tparam match {
2504-
case tparam: Symbol =>
2505-
tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work
2506-
case _ =>
2507-
}
25082502
if (desugaredArg.isType)
25092503
arg match {
25102504
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+
{}

0 commit comments

Comments
 (0)