Skip to content

Commit ec0d0f3

Browse files
committed
Allow simple type args of parent types to back reference
Can't do so for higher-kinded type arguments, as eta-expanding them requires completing them. Nor for applied type arguments, as typing the applied tree type requires completing the type constructor.
1 parent 833a41e commit ec0d0f3

11 files changed

+99
-19
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,9 @@ object SymDenotations {
695695
containsOpaques ||
696696
is(Module, butNot = Package) && owner.seesOpaques
697697

698+
def isTouched(using Context): Boolean =
699+
flagsUNSAFE.is(Touched) // do not force the info to check the flag
700+
698701
def isProvisional(using Context): Boolean =
699702
flagsUNSAFE.is(Provisional) // do not force the info to check the flag
700703

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,16 @@ class TypeApplications(val self: Type) extends AnyVal {
279279
case tp: TypeRef => tp.symbol == defn.AnyKindClass
280280
case _ => false
281281
}
282-
val selfResult = self.hkResult
283-
val otherResult = other.hkResult
284-
isAnyKind(selfResult) || isAnyKind(otherResult) ||
285-
{ if (selfResult.exists)
286-
otherResult.exists &&
287-
selfResult.hasSameKindAs(otherResult) &&
288-
self.typeParams.corresponds(other.typeParams)((sparam, oparam) =>
289-
sparam.paramInfo.hasSameKindAs(oparam.paramInfo))
290-
else !otherResult.exists
291-
}
282+
val sparams = self.typeParams
283+
val oparams = other.typeParams
284+
if sparams.isEmpty && oparams.isEmpty then true // defer calling hkResult to avoid completing types
285+
else
286+
val selfResult = self.hkResult
287+
val otherResult = other.hkResult
288+
isAnyKind(selfResult) || isAnyKind(otherResult)
289+
|| !selfResult.exists && !otherResult.exists
290+
|| selfResult.hasSameKindAs(otherResult)
291+
&& sparams.corresponds(oparams)((sp, op) => sp.paramInfo.hasSameKindAs(op.paramInfo))
292292
}
293293

294294
/** Dealias type if it can be done without forcing the TypeRef's info */

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ object Checking {
170170
* A NoType paramBounds is used as a sign that checking should be suppressed.
171171
*/
172172
def preCheckKind(arg: Tree, paramBounds: Type)(using Context): Tree =
173-
if (arg.tpe.widen.isRef(defn.NothingClass) ||
173+
if (arg.tpe.isExactlyNothing ||
174174
!paramBounds.exists ||
175175
arg.tpe.hasSameKindAs(paramBounds.bounds.hi)) arg
176176
else errorTree(arg, em"Type argument ${arg.tpe} does not have the same kind as its bound $paramBounds")
@@ -336,7 +336,29 @@ object Checking {
336336
if (locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter])
337337
throw CyclicReference(tp.symbol)
338338
locked += tp
339-
try if (!tp.symbol.isClass) checkInfo(tp.info)
339+
try
340+
if !tp.symbol.isClass
341+
&& (!cycleOK || tp.symbol.isTouched)
342+
// from pos/i8900-cycle.scala
343+
// [T <: Cov[U], U <: T]
344+
//
345+
// if we force U while checking T
346+
// then U <: T will encounter T with a NoCompleter
347+
// and throw while cycleOK=false (bad)
348+
//
349+
// so instead we delay U to later
350+
// then U <: T will see T <: Cov[U] and then encounter U with a NoCompleter
351+
// and throw but then cycleOK=true because it's a type argument
352+
//
353+
// for any case of true illegal cycles, e.g. T1 in neg/cycles.scala
354+
// type X = (U, U) // error: cycle (old)
355+
// type U = X & Int // error: cycle (new)
356+
// skipping untouched U will move the error to when U is completed
357+
//
358+
// previously running preCheckKind while typing Cov[U] and (U, U) forced the `U`s
359+
// now completion is avoided in hasSameKindAs by looking at type parameters, leaving the `U`s untouched
360+
then
361+
checkInfo(tp.info)
340362
finally locked -= tp
341363
tp.withPrefix(pre1)
342364
}

tests/neg/cycles.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ class E {
3030
}
3131

3232
class T1 {
33-
type X = (U, U) // error: cycle
34-
type U = X & Int
33+
type X = (U, U)
34+
type U = X & Int // error: cycle
3535
}
3636
class T2 {
37-
type X = (U, U) // error: cycle
38-
type U = X | Int
37+
type X = (U, U)
38+
type U = X | Int // error: cycle
3939
}
4040
object T12 {
4141
val _ : (T1 {})#U = ??? // old-error: conflicting bounds

tests/neg/i15177.app.scala

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

tests/neg/i15177.hk.scala

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

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

tests/neg/i4368.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ object Test6 {
9898

9999
object Test7 {
100100
class Fix[F[_]] {
101-
class Foo { type R >: F[T] <: F[T] } // error: cyclic
102-
type T = F[Foo#R]
101+
class Foo { type R >: F[T] <: F[T] }
102+
type T = F[Foo#R] // error: cyclic
103103
}
104104

105105
object App {

tests/neg/i4369b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
trait X[R <: Z, Z >: X[R, R] <: X[R, R]] // error // error
1+
trait X[R <: Z, Z >: X[R, R] <: X[R, R]] // error
22
class Z extends X[Z, Z]

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.scala

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

0 commit comments

Comments
 (0)