Skip to content

Commit 129963b

Browse files
authored
Merge pull request #5568 from dotty-staging/fix-#4031-v2
Refinements to realizability
2 parents 909f10a + 483a595 commit 129963b

9 files changed

+70
-25
lines changed

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

+11-7
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ object CheckRealizable {
2121

2222
object NotConcrete extends Realizability(" is not a concrete type")
2323

24-
object NotStable extends Realizability(" is not a stable reference")
25-
2624
class NotFinal(sym: Symbol)(implicit ctx: Context)
2725
extends Realizability(i" refers to nonfinal $sym")
2826

@@ -73,12 +71,18 @@ class CheckRealizable(implicit ctx: Context) {
7371
if (sym.is(Stable)) realizability(tp.prefix)
7472
else {
7573
val r =
76-
if (!sym.isStable) NotStable
77-
else if (!isLateInitialized(sym)) Realizable
78-
else if (!sym.isEffectivelyFinal) new NotFinal(sym)
79-
else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r))
74+
if (sym.isStable && !isLateInitialized(sym))
75+
// it's realizable because we know that a value of type `tp` has been created at run-time
76+
Realizable
77+
else if (!sym.isEffectivelyFinal)
78+
// it's potentially not realizable since it might be overridden with a member of nonrealizable type
79+
new NotFinal(sym)
80+
else
81+
// otherwise we need to look at the info to determine realizability
82+
// roughly: it's realizable if the info does not have bad bounds
83+
realizability(tp.info).mapError(r => new ProblemInUnderlying(tp, r))
8084
r andAlso {
81-
sym.setFlag(Stable)
85+
if (sym.isStable) sym.setFlag(Stable) // it's known to be stable and realizable
8286
realizability(tp.prefix)
8387
}
8488
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,10 @@ object SymDenotations {
606606
)
607607

608608
/** Is this a denotation of a stable term (or an arbitrary type)? */
609-
final def isStable(implicit ctx: Context): Boolean =
610-
isType || is(StableOrErased) || !is(UnstableValue) && !info.isInstanceOf[ExprType]
609+
final def isStable(implicit ctx: Context): Boolean = {
610+
def isUnstableValue = is(UnstableValue) || info.isInstanceOf[ExprType]
611+
isType || is(Stable) || !isUnstableValue
612+
}
611613

612614
/** Is this a denotation of a class that does not have - either direct or inherited -
613615
* initaliazion code?

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
319319
thirdTry
320320
case tp1: TypeParamRef =>
321321
def flagNothingBound = {
322-
if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) {
322+
if (!frozenConstraint && tp2.isRef(NothingClass) && state.isGlobalCommittable) {
323323
def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}"
324324
if (Config.failOnInstantiationToNothing) assert(false, msg)
325325
else ctx.log(msg)
@@ -404,11 +404,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
404404
if (cls2.isClass) {
405405
if (cls2.typeParams.isEmpty) {
406406
if (cls2 eq AnyKindClass) return true
407-
if (tp1.isRef(defn.NothingClass)) return true
407+
if (tp1.isRef(NothingClass)) return true
408408
if (tp1.isLambdaSub) return false
409409
// Note: We would like to replace this by `if (tp1.hasHigherKind)`
410410
// but right now we cannot since some parts of the standard library rely on the
411411
// idiom that e.g. `List <: Any`. We have to bootstrap without scalac first.
412+
if (cls2 eq AnyClass) return true
412413
if (cls2 == defn.SingletonClass && tp1.isStable) return true
413414
return tryBaseType(cls2)
414415
}
@@ -417,7 +418,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
417418
val base = tp1.baseType(cls2)
418419
if (base.typeSymbol == cls2) return true
419420
}
420-
else if (tp1.isLambdaSub && !tp1.isRef(defn.AnyKindClass))
421+
else if (tp1.isLambdaSub && !tp1.isRef(AnyKindClass))
421422
return recur(tp1, EtaExpansion(cls2.typeRef))
422423
}
423424
fourthTry
@@ -1382,7 +1383,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
13821383
// at run time. It would not work to replace that with `Nothing`.
13831384
// However, maybe we can still apply the replacement to
13841385
// types which are not explicitly written.
1385-
defn.NothingType
1386+
NothingType
13861387
case _ => andType(tp1, tp2)
13871388
}
13881389
case _ => andType(tp1, tp2)
@@ -1393,8 +1394,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
13931394
}
13941395

13951396
/** The greatest lower bound of a list types */
1396-
final def glb(tps: List[Type]): Type =
1397-
((defn.AnyType: Type) /: tps)(glb)
1397+
final def glb(tps: List[Type]): Type = ((AnyType: Type) /: tps)(glb)
13981398

13991399
/** The least upper bound of two types
14001400
* @param canConstrain If true, new constraints might be added to simplify the lub.
@@ -1424,7 +1424,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
14241424

14251425
/** The least upper bound of a list of types */
14261426
final def lub(tps: List[Type]): Type =
1427-
((defn.NothingType: Type) /: tps)(lub(_,_, canConstrain = false))
1427+
((NothingType: Type) /: tps)(lub(_,_, canConstrain = false))
14281428

14291429
/** Try to produce joint arguments for a lub `A[T_1, ..., T_n] | A[T_1', ..., T_n']` using
14301430
* the following strategies:

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ object Types {
149149

150150
/** Does this type denote a stable reference (i.e. singleton type)? */
151151
final def isStable(implicit ctx: Context): Boolean = stripTypeVar match {
152-
case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable || tp.info.isStable
152+
case tp: TermRef => tp.symbol.isStable && tp.prefix.isStable || tp.info.isStable
153153
case _: SingletonType | NoPrefix => true
154154
case tp: RefinedOrRecType => tp.parent.isStable
155155
case tp: ExprType => tp.resultType.isStable
@@ -4514,6 +4514,8 @@ object Types {
45144514
else if (lo `eq` hi) lo
45154515
else Range(lower(lo), upper(hi))
45164516

4517+
protected def emptyRange = range(defn.NothingType, defn.AnyType)
4518+
45174519
protected def isRange(tp: Type): Boolean = tp.isInstanceOf[Range]
45184520

45194521
protected def lower(tp: Type): Type = tp match {
@@ -4638,7 +4640,7 @@ object Types {
46384640
else tp.derivedTypeBounds(lo, hi)
46394641

46404642
override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type): Type =
4641-
if (isRange(thistp) || isRange(supertp)) range(defn.NothingType, defn.AnyType)
4643+
if (isRange(thistp) || isRange(supertp)) emptyRange
46424644
else tp.derivedSuperType(thistp, supertp)
46434645

46444646
override protected def derivedAppliedType(tp: AppliedType, tycon: Type, args: List[Type]): Type =

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ class RefChecks extends MiniPhase { thisPhase =>
979979
checkAllOverrides(cls)
980980
tree
981981
} catch {
982-
case ex: MergeError =>
982+
case ex: TypeError =>
983983
ctx.error(ex.getMessage, tree.pos)
984984
tree
985985
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ trait TypeAssigner {
105105
case info: ClassInfo =>
106106
range(defn.NothingType, apply(classBound(info)))
107107
case _ =>
108-
range(defn.NothingType, defn.AnyType) // should happen only in error cases
108+
emptyRange // should happen only in error cases
109109
}
110110
case tp: ThisType if toAvoid(tp.cls) =>
111111
range(defn.NothingType, apply(classBound(tp.cls.classInfo)))

tests/neg/i50-volatile.scala

+12-5
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,23 @@ class Test {
1010
}
1111
lazy val o: A & B = ???
1212

13-
class Client extends o.Inner // old-error // old-error
13+
class Client extends o.Inner // error
1414

15-
def xToString(x: o.X): String = x // old-error
15+
def xToString(x: o.X): String = x // error
1616

1717
def intToString(i: Int): String = xToString(i)
1818
}
19-
object Test2 {
2019

21-
import Test.o._ // error
20+
object Test2 {
21+
trait A {
22+
type X = String
23+
}
24+
trait B {
25+
type X = Int
26+
}
27+
lazy val o: A & B = ???
2228

23-
def xToString(x: X): String = x
29+
def xToString(x: o.X): String = x // error
2430

31+
def intToString(i: Int): String = xToString(i)
2532
}

tests/neg/realizability.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class C { type T }
2+
3+
class Test {
4+
5+
type D <: C
6+
7+
lazy val a: C = ???
8+
final lazy val b: C = ???
9+
val c: D = ???
10+
final lazy val d: D = ???
11+
12+
val x1: a.T = ??? // error: not a legal path, since a is lazy & non-final
13+
val x2: b.T = ??? // OK, b is lazy but concrete
14+
val x3: c.T = ??? // OK, c is abstract but strict
15+
val x4: d.T = ??? // error: not a legal path since d is abstract and lazy
16+
17+
val y1: Singleton = a
18+
val y2: Singleton = a
19+
val y3: Singleton = a
20+
val y4: Singleton = a
21+
22+
}

tests/pos/i4031.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object App {
2+
trait A { type L >: Any}
3+
def upcast(a: A, x: Any): a.L = x
4+
lazy val p: A { type L <: Nothing } = p
5+
val q = new A { type L = Any }
6+
def coerce1(x: Any): Any = upcast(q, x) // ok
7+
def coerce3(x: Any): Any = upcast(p, x) // ok, since dependent result type is not needed
8+
}

0 commit comments

Comments
 (0)