From b339481f0a3dba190b94b837b02a8531b47219ca Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 10 Jun 2024 22:57:09 +0100 Subject: [PATCH] Avoid forcing ctors & parents which caused cycles --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/core/NamerOps.scala | 2 +- .../tools/dotc/core/TypeApplications.scala | 4 +- .../src/dotty/tools/dotc/core/Types.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 157 +++++++++++------- .../src/dotty/tools/dotc/typer/Typer.scala | 6 - tests/neg/i15177.FakeEnum.min.alt1.scala | 4 + tests/neg/i15177.constr-dep.scala | 5 + tests/neg/i15177.ub.scala | 13 ++ tests/pos/i15177.FakeEnum.min.alt2.scala | 4 + tests/pos/i15177.FakeEnum.min.alt3.scala | 4 + tests/pos/i15177.FakeEnum.min.scala | 3 + tests/pos/i15177.FakeEnum.scala | 20 +++ tests/pos/i15177.app.scala | 10 ++ tests/pos/i15177.constr-dep.scala | 2 + tests/pos/i15177.hk.scala | 5 + tests/pos/i15177.hk2.scala | 5 + tests/pos/i15177.hylolib.scala | 11 ++ tests/pos/i15177.scala | 3 + tests/pos/i15177.without.scala | 3 + tests/pos/parsercombinators-pc.scala | 9 + 21 files changed, 202 insertions(+), 74 deletions(-) create mode 100644 tests/neg/i15177.FakeEnum.min.alt1.scala create mode 100644 tests/neg/i15177.constr-dep.scala create mode 100644 tests/neg/i15177.ub.scala create mode 100644 tests/pos/i15177.FakeEnum.min.alt2.scala create mode 100644 tests/pos/i15177.FakeEnum.min.alt3.scala create mode 100644 tests/pos/i15177.FakeEnum.min.scala create mode 100644 tests/pos/i15177.FakeEnum.scala create mode 100644 tests/pos/i15177.app.scala create mode 100644 tests/pos/i15177.constr-dep.scala create mode 100644 tests/pos/i15177.hk.scala create mode 100644 tests/pos/i15177.hk2.scala create mode 100644 tests/pos/i15177.hylolib.scala create mode 100644 tests/pos/i15177.scala create mode 100644 tests/pos/i15177.without.scala create mode 100644 tests/pos/parsercombinators-pc.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index b1b771bc7512..f72a9590a077 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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 _ => } diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 07cb9292baa4..363a01665564 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -272,7 +272,7 @@ object NamerOps: * where * * 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. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 54636ff4ad58..85013992278b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index eeffc41d4159..ef99c051ab68 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 83964417a6f1..c87106ab2c5a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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 @@ -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 = @@ -986,7 +991,7 @@ 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 = @@ -994,7 +999,8 @@ class Namer { typer: Typer => !owner.exists || owner.is(Touched) || { - owner.ensureCompleted() + if owner.isType then + owner.ensureCompleted() !symd.isCompleted } @@ -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] @@ -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: @@ -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)) @@ -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. @@ -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, diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e44da20814dd..dd6e0ca7fb24 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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() diff --git a/tests/neg/i15177.FakeEnum.min.alt1.scala b/tests/neg/i15177.FakeEnum.min.alt1.scala new file mode 100644 index 000000000000..ad92f3cddcd0 --- /dev/null +++ b/tests/neg/i15177.FakeEnum.min.alt1.scala @@ -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 diff --git a/tests/neg/i15177.constr-dep.scala b/tests/neg/i15177.constr-dep.scala new file mode 100644 index 000000000000..85608a23668e --- /dev/null +++ b/tests/neg/i15177.constr-dep.scala @@ -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 + ] diff --git a/tests/neg/i15177.ub.scala b/tests/neg/i15177.ub.scala new file mode 100644 index 000000000000..d504528572ed --- /dev/null +++ b/tests/neg/i15177.ub.scala @@ -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 diff --git a/tests/pos/i15177.FakeEnum.min.alt2.scala b/tests/pos/i15177.FakeEnum.min.alt2.scala new file mode 100644 index 000000000000..fd5f87c649ef --- /dev/null +++ b/tests/pos/i15177.FakeEnum.min.alt2.scala @@ -0,0 +1,4 @@ +trait Foo +trait X[T <: Foo] { trait Id } +object A extends X[B] +class B extends A.Id with Foo diff --git a/tests/pos/i15177.FakeEnum.min.alt3.scala b/tests/pos/i15177.FakeEnum.min.alt3.scala new file mode 100644 index 000000000000..b0a16f634bd4 --- /dev/null +++ b/tests/pos/i15177.FakeEnum.min.alt3.scala @@ -0,0 +1,4 @@ +trait Foo +trait X[T <: Foo] { trait Id extends Foo } +object A extends X[B] +class B extends A.Id diff --git a/tests/pos/i15177.FakeEnum.min.scala b/tests/pos/i15177.FakeEnum.min.scala new file mode 100644 index 000000000000..0c426d159b1e --- /dev/null +++ b/tests/pos/i15177.FakeEnum.min.scala @@ -0,0 +1,3 @@ +trait X[T] { trait Id } +object A extends X[B] +class B extends A.Id diff --git a/tests/pos/i15177.FakeEnum.scala b/tests/pos/i15177.FakeEnum.scala new file mode 100644 index 000000000000..e154866fe513 --- /dev/null +++ b/tests/pos/i15177.FakeEnum.scala @@ -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 +{} diff --git a/tests/pos/i15177.app.scala b/tests/pos/i15177.app.scala new file mode 100644 index 000000000000..5f602e7c0593 --- /dev/null +++ b/tests/pos/i15177.app.scala @@ -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 diff --git a/tests/pos/i15177.constr-dep.scala b/tests/pos/i15177.constr-dep.scala new file mode 100644 index 000000000000..f225d14c78ec --- /dev/null +++ b/tests/pos/i15177.constr-dep.scala @@ -0,0 +1,2 @@ +class Bar(val y: Long) +class Bar1(val z: Long) extends Bar(z) diff --git a/tests/pos/i15177.hk.scala b/tests/pos/i15177.hk.scala new file mode 100644 index 000000000000..4530e6c960e2 --- /dev/null +++ b/tests/pos/i15177.hk.scala @@ -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) diff --git a/tests/pos/i15177.hk2.scala b/tests/pos/i15177.hk2.scala new file mode 100644 index 000000000000..f5dce9020dc6 --- /dev/null +++ b/tests/pos/i15177.hk2.scala @@ -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] \ No newline at end of file diff --git a/tests/pos/i15177.hylolib.scala b/tests/pos/i15177.hylolib.scala new file mode 100644 index 000000000000..96cf87680a1c --- /dev/null +++ b/tests/pos/i15177.hylolib.scala @@ -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 = ??? diff --git a/tests/pos/i15177.scala b/tests/pos/i15177.scala new file mode 100644 index 000000000000..278994961810 --- /dev/null +++ b/tests/pos/i15177.scala @@ -0,0 +1,3 @@ +class X[T] { trait Id } +object A extends X[B] +class B(id: A.Id) diff --git a/tests/pos/i15177.without.scala b/tests/pos/i15177.without.scala new file mode 100644 index 000000000000..ca40bc096597 --- /dev/null +++ b/tests/pos/i15177.without.scala @@ -0,0 +1,3 @@ +class X[T] { trait Id } +class A extends X[B] +class B diff --git a/tests/pos/parsercombinators-pc.scala b/tests/pos/parsercombinators-pc.scala new file mode 100644 index 000000000000..e18fbb2d1c33 --- /dev/null +++ b/tests/pos/parsercombinators-pc.scala @@ -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