Skip to content

Commit b339481

Browse files
committed
Avoid forcing ctors & parents which caused cycles
1 parent 7bdeb0b commit b339481

21 files changed

+202
-74
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ 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 =>
9696
completer.completeConstructor(sym)
9797
case _ =>
9898
}

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) 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
@@ -196,7 +196,9 @@ object Types extends TypeUtils {
196196
*/
197197
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = this match {
198198
case this1: TypeRef =>
199-
this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
199+
// avoid forcing symbol if it's a class, not a type alias
200+
if this1.symbol.isClass then this1.symbol eq sym
201+
else this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
200202
case TypeAlias(tp) => tp.isRef(sym, skipRefined)
201203
case _ => this1.symbol eq sym
202204
}

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

Lines changed: 93 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,7 @@ 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+
val typer1 = newNestedTyper(sym)
827826
typer1.defDefSig(original, sym, this)(using localContext(sym).setTyper(typer1))
828827
case imp: Import =>
829828
try
@@ -833,6 +832,12 @@ class Namer { typer: Typer =>
833832
typr.println(s"error while completing ${imp.expr}")
834833
throw ex
835834

835+
def newNestedTyper(sym: Symbol) = nestedTyper.getOrElseUpdate(sym, ctx.typer.newLikeThis(ctx.nestingLevel + 1))
836+
837+
def indexConstructor(constr: DefDef, sym: Symbol): Unit =
838+
val typer1 = newNestedTyper(sym)
839+
typer1.indexConstructor(constr, sym)(using localContext(sym).setTyper(typer1))
840+
836841
final override def complete(denot: SymDenotation)(using Context): Unit = {
837842
if (Config.showCompletions && ctx.typerState != creationContext.typerState) {
838843
def levels(c: Context): Int =
@@ -986,15 +991,16 @@ class Namer { typer: Typer =>
986991

987992
/** If completion of the owner of the to be completed symbol has not yet started,
988993
* complete the owner first and check again. This prevents cyclic references
989-
* where we need to copmplete a type parameter that has an owner that is not
994+
* where we need to complete a type parameter that has an owner that is not
990995
* yet completed. Test case is pos/i10967.scala.
991996
*/
992997
override def needsCompletion(symd: SymDenotation)(using Context): Boolean =
993998
val owner = symd.owner
994999
!owner.exists
9951000
|| owner.is(Touched)
9961001
|| {
997-
owner.ensureCompleted()
1002+
if owner.isType then
1003+
owner.ensureCompleted()
9981004
!symd.isCompleted
9991005
}
10001006

@@ -1519,12 +1525,9 @@ class Namer { typer: Typer =>
15191525
index(constr)
15201526
index(rest)(using localCtx)
15211527

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

15301533
tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo]
@@ -1853,31 +1856,6 @@ class Namer { typer: Typer =>
18531856
// Beware: ddef.name need not match sym.name if sym was freshened!
18541857
val isConstructor = sym.name == nme.CONSTRUCTOR
18551858

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

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

19441898
if isConstructor then
1945-
if sym.isPrimaryConstructor && Feature.enabled(modularity) then
1946-
ddef.termParamss.foreach(_.foreach(setTracked))
19471899
// set result type tree to unit, but take the current class as result type of the symbol
19481900
typedAheadType(ddef.tpt, defn.UnitType)
1949-
wrapMethType(effectiveResultType(sym, paramSymss))
1901+
val mt = wrapMethType(effectiveResultType(sym, paramSymss))
1902+
if sym.isPrimaryConstructor then
1903+
mt.stripPoly match
1904+
case mt: MethodType if sym.owner.is(Case) && mt.isParamDependent =>
1905+
// See issue #8073 for background
1906+
report.error(
1907+
em"""Implementation restriction: case classes cannot have dependencies between parameters""",
1908+
sym.owner.srcPos)
1909+
case _ =>
1910+
mt
19501911
else if sym.isAllOf(Given | Method) && Feature.enabled(modularity) then
19511912
// set every context bound evidence parameter of a given companion method
19521913
// to be tracked, provided it has a type that has an abstract type member.
@@ -1959,6 +1920,74 @@ class Namer { typer: Typer =>
19591920
valOrDefDefSig(ddef, sym, paramSymss, wrapMethType)
19601921
end defDefSig
19611922

1923+
def indexConstructor(constr: DefDef, sym: Symbol)(using Context): Unit =
1924+
index(constr.leadingTypeParams)
1925+
sym.owner.typeParams.foreach(_.ensureCompleted())
1926+
completeTrailingParamss(constr, sym, indexingCtor = true)
1927+
if Feature.enabled(modularity) then
1928+
constr.termParamss.foreach(_.foreach(setTracked))
1929+
1930+
def completeTrailingParamss(ddef: DefDef, sym: Symbol, indexingCtor: Boolean = false)(using Context): Unit =
1931+
// A map from context-bounded type parameters to associated evidence parameter names
1932+
val witnessNamesOfParam = mutable.Map[TypeDef, List[TermName]]()
1933+
if !ddef.name.is(DefaultGetterName) && !sym.is(Synthetic) && (indexingCtor || !sym.isPrimaryConstructor) then
1934+
for params <- ddef.paramss; case tdef: TypeDef <- params do
1935+
for case WitnessNamesAnnot(ws) <- tdef.mods.annotations do
1936+
witnessNamesOfParam(tdef) = ws
1937+
1938+
/** Is each name in `wnames` defined somewhere in the previous parameters? */
1939+
def allParamsSeen(wnames: List[TermName], prevParams1: List[Name]) =
1940+
(wnames.toSet[Name] -- prevParams1).isEmpty
1941+
1942+
/** Enter and typecheck parameter list.
1943+
* Once all witness parameters for a context bound are seen, create a
1944+
* context bound companion for it.
1945+
*/
1946+
def completeParams(params: List[MemberDef])(using Context): Unit =
1947+
if indexingCtor || !sym.isPrimaryConstructor then index(params)
1948+
val paramSyms = params.map(symbolOfTree)
1949+
1950+
def loop(nextParams: List[MemberDef], prevParams: List[Name]): Unit = nextParams match
1951+
case param :: nextParams1 =>
1952+
if !indexingCtor then
1953+
typedAheadExpr(param)
1954+
1955+
val prevParams1 = param.name :: prevParams
1956+
for (tdef, wnames) <- witnessNamesOfParam do
1957+
if wnames.contains(param.name) && allParamsSeen(wnames, prevParams1) then
1958+
addContextBoundCompanionFor(symbolOfTree(tdef), wnames, paramSyms)
1959+
1960+
loop(nextParams1, prevParams1)
1961+
case _ =>
1962+
loop(params, Nil)
1963+
end completeParams
1964+
1965+
ddef.trailingParamss.foreach(completeParams)
1966+
end completeTrailingParamss
1967+
1968+
/** Under x.modularity, we add `tracked` to context bound witnesses
1969+
* that have abstract type members
1970+
*/
1971+
def needsTracked(sym: Symbol, param: ValDef)(using Context) =
1972+
!sym.is(Tracked)
1973+
&& param.hasAttachment(ContextBoundParam)
1974+
&& sym.info.memberNames(abstractTypeNameFilter).nonEmpty
1975+
1976+
/** Under x.modularity, set every context bound evidence parameter of a class to be tracked,
1977+
* provided it has a type that has an abstract type member. Reset private and local flags
1978+
* so that the parameter becomes a `val`.
1979+
*/
1980+
def setTracked(param: ValDef)(using Context): Unit =
1981+
val sym = symbolOfTree(param)
1982+
sym.maybeOwner.maybeOwner.infoOrCompleter match
1983+
case info: ClassInfo if needsTracked(sym, param) =>
1984+
typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}")
1985+
for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do
1986+
acc.resetFlag(PrivateLocal)
1987+
acc.setFlag(Tracked)
1988+
sym.setFlag(Tracked)
1989+
case _ =>
1990+
19621991
def inferredResultType(
19631992
mdef: ValOrDefDef,
19641993
sym: Symbol,

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,12 +2472,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24722472
(arg, tparamBounds)
24732473
else
24742474
(arg, WildcardType)
2475-
if (tpt1.symbol.isClass)
2476-
tparam match {
2477-
case tparam: Symbol =>
2478-
tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work
2479-
case _ =>
2480-
}
24812475
if (desugaredArg.isType)
24822476
arg match {
24832477
case untpd.WildcardTypeBoundsTree()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
trait Foo
2+
trait X[T <: Foo] { trait Id }
3+
object A extends X[B] // error: Type argument B does not conform to upper bound Foo
4+
class B extends A.Id

tests/neg/i15177.constr-dep.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Foo[A]
2+
class Foo1(val x: Int)
3+
extends Foo[ // error: The type of a class parent cannot refer to constructor parameters, but Foo[(Foo1.this.x : Int)] refers to x
4+
x.type
5+
]

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
trait Foo
2+
trait X[T <: Foo] { trait Id }
3+
object A extends X[B]
4+
class B extends A.Id with Foo
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
trait Foo
2+
trait X[T <: Foo] { trait Id extends Foo }
3+
object A extends X[B]
4+
class B extends A.Id

tests/pos/i15177.FakeEnum.min.scala

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

tests/pos/i15177.FakeEnum.scala

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

tests/pos/i15177.app.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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[
6+
C]( // error: Something's wrong: missing original symbol for type tree
7+
id:
8+
A
9+
.Id) // should-be-error: type Id is not a member of object A
10+
class D

tests/pos/i15177.constr-dep.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Bar(val y: Long)
2+
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: 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+
class A extends X[B]
5+
class B[C]

tests/pos/i15177.hylolib.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//> using options -language:experimental.modularity -source future
2+
// A minimisation of pos/hylolib-cb that broke while fixing i15177
3+
trait Value[Self]
4+
trait Coll[Self]:
5+
type Pos: Value
6+
extension (self: Self) def pos: Pos
7+
extension [Self: Coll](self: Self) def trigger = self.pos
8+
class Slice[Base]
9+
given SliceIsColl[T: Coll as c]: Coll[Slice[T]] with
10+
type Pos = c.Pos
11+
extension (self: Slice[T]) def pos: Pos = ???

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class X[T] { trait Id }
2+
class A extends X[B]
3+
class B

tests/pos/parsercombinators-pc.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -language:experimental.modularity -source future
2+
3+
trait Foo:
4+
type Self
5+
type Bar
6+
7+
given inst[A: Foo, B: Foo { type Bar = A.Bar }]: Foo with
8+
type Self = String
9+
type Bar = A.Bar

0 commit comments

Comments
 (0)