Skip to content

Commit

Permalink
Avoid forcing ctors & parents which caused cycles
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand committed Jun 11, 2024
1 parent 7bdeb0b commit b339481
Show file tree
Hide file tree
Showing 21 changed files with 202 additions and 74 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object desugar {
override def ensureCompletions(using Context): Unit = {
def completeConstructor(sym: Symbol) =
sym.infoOrCompleter match {
case completer: Namer#ClassCompleter =>
case completer: Namer#ClassCompleter if !sym.isCompleting =>
completer.completeConstructor(sym)
case _ =>
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ object NamerOps:
* where
*
* <context-bound-companion> is the CBCompanion type created in Definitions
* withnessRefK is a refence to the K'th witness.
* withnessRefK is a reference to the K'th witness.
*
* The companion has the same access flags as the original type.
*/
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ class TypeApplications(val self: Type) extends AnyVal {
*/
def hkResult(using Context): Type = self.dealias match {
case self: TypeRef =>
if (self.symbol == defn.AnyKindClass) self else self.info.hkResult
if (self.symbol == defn.AnyKindClass) self
else if self.symbol.isClass then NoType // avoid forcing symbol if it's a class, not an alias to a HK type lambda
else self.info.hkResult
case self: AppliedType =>
if (self.tycon.typeSymbol.isClass) NoType else self.superType.hkResult
case self: HKTypeLambda => self.resultType
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ object Types extends TypeUtils {
*/
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = this match {
case this1: TypeRef =>
this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
// avoid forcing symbol if it's a class, not a type alias
if this1.symbol.isClass then this1.symbol eq sym
else this1.info match { // see comment in Namer#TypeDefCompleter#typeSig
case TypeAlias(tp) => tp.isRef(sym, skipRefined)
case _ => this1.symbol eq sym
}
Expand Down
157 changes: 93 additions & 64 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -822,8 +822,7 @@ class Namer { typer: Typer =>
if (sym.is(Module)) moduleValSig(sym)
else valOrDefDefSig(original, sym, Nil, identity)(using localContext(sym).setNewScope)
case original: DefDef =>
val typer1 = ctx.typer.newLikeThis(ctx.nestingLevel + 1)
nestedTyper(sym) = typer1
val typer1 = newNestedTyper(sym)
typer1.defDefSig(original, sym, this)(using localContext(sym).setTyper(typer1))
case imp: Import =>
try
Expand All @@ -833,6 +832,12 @@ class Namer { typer: Typer =>
typr.println(s"error while completing ${imp.expr}")
throw ex

def newNestedTyper(sym: Symbol) = nestedTyper.getOrElseUpdate(sym, ctx.typer.newLikeThis(ctx.nestingLevel + 1))

def indexConstructor(constr: DefDef, sym: Symbol): Unit =
val typer1 = newNestedTyper(sym)
typer1.indexConstructor(constr, sym)(using localContext(sym).setTyper(typer1))

final override def complete(denot: SymDenotation)(using Context): Unit = {
if (Config.showCompletions && ctx.typerState != creationContext.typerState) {
def levels(c: Context): Int =
Expand Down Expand Up @@ -986,15 +991,16 @@ class Namer { typer: Typer =>

/** If completion of the owner of the to be completed symbol has not yet started,
* complete the owner first and check again. This prevents cyclic references
* where we need to copmplete a type parameter that has an owner that is not
* where we need to complete a type parameter that has an owner that is not
* yet completed. Test case is pos/i10967.scala.
*/
override def needsCompletion(symd: SymDenotation)(using Context): Boolean =
val owner = symd.owner
!owner.exists
|| owner.is(Touched)
|| {
owner.ensureCompleted()
if owner.isType then
owner.ensureCompleted()
!symd.isCompleted
}

Expand Down Expand Up @@ -1519,12 +1525,9 @@ class Namer { typer: Typer =>
index(constr)
index(rest)(using localCtx)

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

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

// A map from context-bounded type parameters to associated evidence parameter names
val witnessNamesOfParam = mutable.Map[TypeDef, List[TermName]]()
if !ddef.name.is(DefaultGetterName) && !sym.is(Synthetic) then
for params <- ddef.paramss; case tdef: TypeDef <- params do
for case WitnessNamesAnnot(ws) <- tdef.mods.annotations do
witnessNamesOfParam(tdef) = ws

/** Is each name in `wnames` defined somewhere in the longest prefix of all `params`
* that have been typed ahead (i.e. that carry the TypedAhead attachment)?
*/
def allParamsSeen(wnames: List[TermName], params: List[MemberDef]) =
(wnames.toSet[Name] -- params.takeWhile(_.hasAttachment(TypedAhead)).map(_.name)).isEmpty

/** Enter and typecheck parameter list.
* Once all witness parameters for a context bound are seen, create a
* context bound companion for it.
*/
def completeParams(params: List[MemberDef])(using Context): Unit =
index(params)
for param <- params do
typedAheadExpr(param)
for (tdef, wnames) <- witnessNamesOfParam do
if wnames.contains(param.name) && allParamsSeen(wnames, params) then
addContextBoundCompanionFor(symbolOfTree(tdef), wnames, params.map(symbolOfTree))

// The following 3 lines replace what was previously just completeParams(tparams).
// But that can cause bad bounds being computed, as witnessed by
// tests/pos/paramcycle.scala. The problematic sequence is this:
Expand All @@ -1901,39 +1879,15 @@ class Namer { typer: Typer =>
// 3. Info of CP is computed (to be copied to DP).
// 4. CP is completed.
// 5. Info of CP is copied to DP and DP is completed.
index(ddef.leadingTypeParams)
if (isConstructor) sym.owner.typeParams.foreach(_.ensureCompleted())
if !sym.isPrimaryConstructor then index(ddef.leadingTypeParams)
val completedTypeParams =
for tparam <- ddef.leadingTypeParams yield typedAheadExpr(tparam).symbol
if completedTypeParams.forall(_.isType) then
completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]])
ddef.trailingParamss.foreach(completeParams)
completeTrailingParamss(ddef, sym)
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor)
sym.setParamss(paramSymss)

/** Under x.modularity, we add `tracked` to context bound witnesses
* that have abstract type members
*/
def needsTracked(sym: Symbol, param: ValDef)(using Context) =
!sym.is(Tracked)
&& param.hasAttachment(ContextBoundParam)
&& sym.info.memberNames(abstractTypeNameFilter).nonEmpty

/** Under x.modularity, set every context bound evidence parameter of a class to be tracked,
* provided it has a type that has an abstract type member. Reset private and local flags
* so that the parameter becomes a `val`.
*/
def setTracked(param: ValDef): Unit =
val sym = symbolOfTree(param)
sym.maybeOwner.maybeOwner.infoOrCompleter match
case info: TempClassInfo if needsTracked(sym, param) =>
typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}")
for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do
acc.resetFlag(PrivateLocal)
acc.setFlag(Tracked)
sym.setFlag(Tracked)
case _ =>

def wrapMethType(restpe: Type): Type =
instantiateDependent(restpe, paramSymss)
methodType(paramSymss, restpe, ddef.mods.is(JavaDefined))
Expand All @@ -1942,11 +1896,18 @@ class Namer { typer: Typer =>
wrapMethType(addParamRefinements(restpe, paramSymss))

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

def indexConstructor(constr: DefDef, sym: Symbol)(using Context): Unit =
index(constr.leadingTypeParams)
sym.owner.typeParams.foreach(_.ensureCompleted())
completeTrailingParamss(constr, sym, indexingCtor = true)
if Feature.enabled(modularity) then
constr.termParamss.foreach(_.foreach(setTracked))

def completeTrailingParamss(ddef: DefDef, sym: Symbol, indexingCtor: Boolean = false)(using Context): Unit =
// A map from context-bounded type parameters to associated evidence parameter names
val witnessNamesOfParam = mutable.Map[TypeDef, List[TermName]]()
if !ddef.name.is(DefaultGetterName) && !sym.is(Synthetic) && (indexingCtor || !sym.isPrimaryConstructor) then
for params <- ddef.paramss; case tdef: TypeDef <- params do
for case WitnessNamesAnnot(ws) <- tdef.mods.annotations do
witnessNamesOfParam(tdef) = ws

/** Is each name in `wnames` defined somewhere in the previous parameters? */
def allParamsSeen(wnames: List[TermName], prevParams1: List[Name]) =
(wnames.toSet[Name] -- prevParams1).isEmpty

/** Enter and typecheck parameter list.
* Once all witness parameters for a context bound are seen, create a
* context bound companion for it.
*/
def completeParams(params: List[MemberDef])(using Context): Unit =
if indexingCtor || !sym.isPrimaryConstructor then index(params)
val paramSyms = params.map(symbolOfTree)

def loop(nextParams: List[MemberDef], prevParams: List[Name]): Unit = nextParams match
case param :: nextParams1 =>
if !indexingCtor then
typedAheadExpr(param)

val prevParams1 = param.name :: prevParams
for (tdef, wnames) <- witnessNamesOfParam do
if wnames.contains(param.name) && allParamsSeen(wnames, prevParams1) then
addContextBoundCompanionFor(symbolOfTree(tdef), wnames, paramSyms)

loop(nextParams1, prevParams1)
case _ =>
loop(params, Nil)
end completeParams

ddef.trailingParamss.foreach(completeParams)
end completeTrailingParamss

/** Under x.modularity, we add `tracked` to context bound witnesses
* that have abstract type members
*/
def needsTracked(sym: Symbol, param: ValDef)(using Context) =
!sym.is(Tracked)
&& param.hasAttachment(ContextBoundParam)
&& sym.info.memberNames(abstractTypeNameFilter).nonEmpty

/** Under x.modularity, set every context bound evidence parameter of a class to be tracked,
* provided it has a type that has an abstract type member. Reset private and local flags
* so that the parameter becomes a `val`.
*/
def setTracked(param: ValDef)(using Context): Unit =
val sym = symbolOfTree(param)
sym.maybeOwner.maybeOwner.infoOrCompleter match
case info: ClassInfo if needsTracked(sym, param) =>
typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}")
for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do
acc.resetFlag(PrivateLocal)
acc.setFlag(Tracked)
sym.setFlag(Tracked)
case _ =>

def inferredResultType(
mdef: ValOrDefDef,
sym: Symbol,
Expand Down
6 changes: 0 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2472,12 +2472,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
(arg, tparamBounds)
else
(arg, WildcardType)
if (tpt1.symbol.isClass)
tparam match {
case tparam: Symbol =>
tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work
case _ =>
}
if (desugaredArg.isType)
arg match {
case untpd.WildcardTypeBoundsTree()
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/i15177.FakeEnum.min.alt1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trait Foo
trait X[T <: Foo] { trait Id }
object A extends X[B] // error: Type argument B does not conform to upper bound Foo
class B extends A.Id
5 changes: 5 additions & 0 deletions tests/neg/i15177.constr-dep.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Foo[A]
class Foo1(val x: Int)
extends Foo[ // error: The type of a class parent cannot refer to constructor parameters, but Foo[(Foo1.this.x : Int)] refers to x
x.type
]
13 changes: 13 additions & 0 deletions tests/neg/i15177.ub.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// like tests/pos/i15177.scala
// but with T having an upper bound
// that B doesn't conform to
// just to be sure that not forcing B
// doesn't backdoor an illegal X[B]
class X[T <: C] {
type Id
}
object A
extends X[ // error
B] // error
class B(id: A.Id)
class C
4 changes: 4 additions & 0 deletions tests/pos/i15177.FakeEnum.min.alt2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trait Foo
trait X[T <: Foo] { trait Id }
object A extends X[B]
class B extends A.Id with Foo
4 changes: 4 additions & 0 deletions tests/pos/i15177.FakeEnum.min.alt3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trait Foo
trait X[T <: Foo] { trait Id extends Foo }
object A extends X[B]
class B extends A.Id
3 changes: 3 additions & 0 deletions tests/pos/i15177.FakeEnum.min.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait X[T] { trait Id }
object A extends X[B]
class B extends A.Id
20 changes: 20 additions & 0 deletions tests/pos/i15177.FakeEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
trait FakeEnum[A, @specialized(Byte, Short, Int, Long) B]
{
trait Value {
self: A =>
def name: String
def id: B
}
}

object FakeEnumType
extends FakeEnum[FakeEnumType, Short]
{
val MEMBER1 = new FakeEnumType((0: Short), "MEMBER1") {}
val MEMBER2 = new FakeEnumType((1: Short), "MEMBER2") {}
}

sealed abstract
class FakeEnumType(val id: Short, val name: String)
extends FakeEnumType.Value
{}
10 changes: 10 additions & 0 deletions tests/pos/i15177.app.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// like tests/pos/i15177.scala
// but with an applied type B[D]
class X[T] { type Id }
object A extends X[B[D]]
class B[
C]( // error: Something's wrong: missing original symbol for type tree
id:
A
.Id) // should-be-error: type Id is not a member of object A
class D
2 changes: 2 additions & 0 deletions tests/pos/i15177.constr-dep.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Bar(val y: Long)
class Bar1(val z: Long) extends Bar(z)
5 changes: 5 additions & 0 deletions tests/pos/i15177.hk.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// like tests/pos/i15177.scala
// but with B being higher kinded
class X[T[_]] { type Id }
object A extends X[B]
class B[C](id: A.Id)
5 changes: 5 additions & 0 deletions tests/pos/i15177.hk2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// like tests/pos/i15177.scala
// but with B being higher kinded
class X[T[_]] { type Id }
class A extends X[B]
class B[C]
11 changes: 11 additions & 0 deletions tests/pos/i15177.hylolib.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//> using options -language:experimental.modularity -source future
// A minimisation of pos/hylolib-cb that broke while fixing i15177
trait Value[Self]
trait Coll[Self]:
type Pos: Value
extension (self: Self) def pos: Pos
extension [Self: Coll](self: Self) def trigger = self.pos
class Slice[Base]
given SliceIsColl[T: Coll as c]: Coll[Slice[T]] with
type Pos = c.Pos
extension (self: Slice[T]) def pos: Pos = ???
3 changes: 3 additions & 0 deletions tests/pos/i15177.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class X[T] { trait Id }
object A extends X[B]
class B(id: A.Id)
3 changes: 3 additions & 0 deletions tests/pos/i15177.without.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class X[T] { trait Id }
class A extends X[B]
class B
9 changes: 9 additions & 0 deletions tests/pos/parsercombinators-pc.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//> using options -language:experimental.modularity -source future

trait Foo:
type Self
type Bar

given inst[A: Foo, B: Foo { type Bar = A.Bar }]: Foo with
type Self = String
type Bar = A.Bar

0 comments on commit b339481

Please sign in to comment.