Skip to content

Realizability: fixes and cleanups of docs #5730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| (fn.symbol.isStable && !fn.symbol.is(Lazy))
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))
|| fn.symbol.isPrimaryConstructor && fn.symbol.owner.isNoInitsClass) // TODO: include in isStable?
minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure
else if (fn.symbol.is(Erased)) Pure
else if (fn.symbol.isStable /* && fn.symbol.is(Lazy) */)
else if (fn.symbol.isStableMember /* && fn.symbol.is(Lazy) */)
minOf(exprPurity(fn), args.map(exprPurity)) `min` Idempotent
else Impure
case Typed(expr, _) =>
Expand Down Expand Up @@ -439,7 +439,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
val sym = tree.symbol
if (!tree.hasType) Impure
else if (!tree.tpe.widen.isParameterless || sym.isEffectivelyErased) SimplyPure
else if (!sym.isStable) Impure
else if (!sym.isStableMember) Impure
else if (sym.is(Module))
if (sym.moduleClass.isNoInitsClass) Pure else Idempotent
else if (sym.is(Lazy)) Idempotent
Expand Down Expand Up @@ -521,7 +521,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case tpe: PolyType => maybeGetterType(tpe.resultType)
case _ => false
}
sym.owner.isClass && !sym.isStable && maybeGetterType(sym.info)
sym.owner.isClass && !sym.isStableMember && maybeGetterType(sym.info)
}

/** Is tree a reference to a mutable variable, or to a potential getter
Expand Down
35 changes: 28 additions & 7 deletions compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import collection.mutable
/** Realizability status */
object CheckRealizable {

abstract class Realizability(val msg: String) {
sealed abstract class Realizability(val msg: String) {
def andAlso(other: => Realizability): Realizability =
if (this == Realizable) other else this
def mapError(f: Realizability => Realizability): Realizability =
Expand Down Expand Up @@ -47,10 +47,18 @@ object CheckRealizable {
def boundsRealizability(tp: Type)(implicit ctx: Context): Realizability =
new CheckRealizable().boundsRealizability(tp)

private val LateInitialized = Lazy | Erased,
private val LateInitialized = Lazy | Erased
}

/** Compute realizability status */
/** Compute realizability status.
Copy link
Contributor Author

@Blaisorblade Blaisorblade Jan 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abeln You asked questions (back in #5558) about stability and realizability, do you have a chance to take a look at this comment?

*
* A type T is realizable iff it is inhabited by non-null values. This ensures that its type members have good bounds
* (in the sense from DOT papers). A type projection T#L is legal if T is realizable, and can be understood as
* Scala 2's `v.L forSome { val v: T }`.
*
* In general, a realizable type can have multiple inhabitants, hence it need not be stable (in the sense of
* Type.isStable).
*/
class CheckRealizable(implicit ctx: Context) {
import CheckRealizable._

Expand All @@ -66,13 +74,22 @@ class CheckRealizable(implicit ctx: Context) {

/** The realizability status of given type `tp`*/
def realizability(tp: Type): Realizability = tp.dealias match {
/*
* A `TermRef` for a path `p` is realizable if
* - `p`'s type is stable and realizable, or
* - its underlying path is idempotent (that is, *stable*), total, and not null.
* We don't check yet the "not null" clause: that will require null-safety checking.
*
* We assume that stability of tp.prefix is checked elsewhere, since that's necessary for the path to be legal in
* the first place.
*/
case tp: TermRef =>
val sym = tp.symbol
lazy val tpInfoRealizable = realizability(tp.info)
if (sym.is(Stable)) realizability(tp.prefix)
if (sym.is(StableRealizable)) realizability(tp.prefix)
else {
val r =
if (sym.isStable && !isLateInitialized(sym))
if (sym.isStableMember && !isLateInitialized(sym))
// it's realizable because we know that a value of type `tp` has been created at run-time
Realizable
else if (!sym.isEffectivelyFinal)
Expand All @@ -83,10 +100,14 @@ class CheckRealizable(implicit ctx: Context) {
// roughly: it's realizable if the info does not have bad bounds
tpInfoRealizable.mapError(r => new ProblemInUnderlying(tp, r))
r andAlso {
if (sym.isStable) sym.setFlag(Stable) // it's known to be stable and realizable
if (sym.isStableMember) sym.setFlag(StableRealizable) // it's known to be stable and realizable
realizability(tp.prefix)
} mapError { r =>
if (tp.info.isStable && tpInfoRealizable == Realizable) Realizable else r
// A mutable path is in fact stable and realizable if it has a realizable singleton type.
if (tp.info.isStable && tpInfoRealizable == Realizable) {
sym.setFlag(StableRealizable)
Realizable
} else r
}
}
case _: SingletonType | NoPrefix =>
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ object Flags {
final val Abstract: FlagSet = commonFlag(23, "abstract")

/** Lazy val or method is known or assumed to be stable and realizable */
final val Stable: FlagSet = termFlag(24, "<stable>")
final val StableRealizable: FlagSet = termFlag(24, "<stable>")

/** A case parameter accessor */
final val CaseAccessor: FlagSet = termFlag(25, "<caseaccessor>")
Expand Down Expand Up @@ -509,7 +509,7 @@ object Flags {
final val RetainedTypeArgFlags: FlagSet = VarianceFlags | Protected | Local

/** Modules always have these flags set */
final val ModuleValCreationFlags: FlagSet = ModuleVal | Lazy | Final | Stable
final val ModuleValCreationFlags: FlagSet = ModuleVal | Lazy | Final | StableRealizable

/** Module classes always have these flags set */
final val ModuleClassCreationFlags: FlagSet = ModuleClass | Final
Expand Down Expand Up @@ -540,7 +540,7 @@ object Flags {
/** Flags that can apply to a module val */
final val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags |
Override | Final | Method | Implicit | Lazy |
Accessor | AbsOverride | Stable | Captured | Synchronized | Erased
Accessor | AbsOverride | StableRealizable | Captured | Synchronized | Erased

/** Flags that can apply to a module class */
final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags |
Expand Down Expand Up @@ -585,7 +585,7 @@ object Flags {
final val InlineOrProxy: FlagSet = Inline | InlineProxy

/** Assumed to be pure */
final val StableOrErased: FlagSet = Stable | Erased
final val StableOrErased: FlagSet = StableRealizable | Erased

/** Labeled `private`, `final`, or `inline` */
final val EffectivelyFinal: FlagSet = Private | Final | Inline
Expand Down Expand Up @@ -678,7 +678,7 @@ object Flags {
final val JavaEnumTrait: FlagConjunction = allOf(JavaDefined, Enum)

/** A Java enum value */
final val JavaEnumValue: FlagConjunction = allOf(Stable, JavaStatic, JavaDefined, Enum)
final val JavaEnumValue: FlagConjunction = allOf(StableRealizable, JavaStatic, JavaDefined, Enum)

/** Labeled private[this] */
final val PrivateLocal: FlagConjunction = allOf(Private, Local)
Expand All @@ -687,7 +687,7 @@ object Flags {
final val PrivateLocalParamAccessor: FlagConjunction = allOf(Private, Local, ParamAccessor)

/** A parameter forwarder */
final val ParamForwarder: FlagConjunction = allOf(Method, Stable, ParamAccessor)
final val ParamForwarder: FlagConjunction = allOf(Method, StableRealizable, ParamAccessor)

/** A private[this] parameter */
final val PrivateLocalParam: FlagConjunction = allOf(Private, Local, Param)
Expand Down
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -608,10 +608,19 @@ object SymDenotations {
}
)

/** Is this a denotation of a stable term (or an arbitrary type)? */
final def isStable(implicit ctx: Context): Boolean = {
/** Is this a denotation of a stable term (or an arbitrary type)?
* Terms are stable if they are idempotent (as in TreeInfo.Idempotent): that is, they always return the same value,
* if any.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relation between stable and idempotent was a bit surprising to me and @odersky, but I think I just convinced him (in a quick chat) this makes sense. Counterexamples welcome!

*
* A *member* is stable, basically, if it behaves like a field projection: that is, it projects a constant result
* out of its owner.
*
* However, a stable member might not yet be initialized (if it is an object or anyhow lazy).
* So the first call to a stable member might fail and/or produce side effects.
*/
final def isStableMember(implicit ctx: Context): Boolean = {
def isUnstableValue = is(UnstableValue) || info.isInstanceOf[ExprType]
isType || is(Stable) || !isUnstableValue
isType || is(StableRealizable) || !isUnstableValue
}

/** Is this a denotation of a class that does not have - either direct or inherited -
Expand Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,13 @@ object Types {
/** Is this a value type or a wildcard? */
final def isValueTypeOrWildcard: Boolean = isValueType || this.isInstanceOf[WildcardType]

/** Does this type denote a stable reference (i.e. singleton type)? */
/** Does this type denote a stable reference (i.e. singleton type)?
*
* Like in isStableMember, "stability" means idempotence.
* Rationale: If an expression has a stable type, the expression must be idempotent, so stable types
* must be singleton types of stable expressions. */
final def isStable(implicit ctx: Context): Boolean = stripTypeVar match {
case tp: TermRef => tp.symbol.isStable && tp.prefix.isStable || tp.info.isStable
case tp: TermRef => tp.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable
case _: SingletonType | NoPrefix => true
case tp: RefinedOrRecType => tp.parent.isStable
case tp: ExprType => tp.resultType.isStable
Expand Down Expand Up @@ -542,7 +546,7 @@ object Types {
case tp: TermRef =>
go (tp.underlying match {
case mt: MethodType
if mt.paramInfos.isEmpty && (tp.symbol is Stable) => mt.resultType
if mt.paramInfos.isEmpty && (tp.symbol is StableRealizable) => mt.resultType
case tp1 => tp1
})
case tp: TypeRef =>
Expand Down Expand Up @@ -1005,7 +1009,7 @@ object Types {
/** Widen type if it is unstable (i.e. an ExprType, or TermRef to unstable symbol */
final def widenIfUnstable(implicit ctx: Context): Type = stripTypeVar match {
case tp: ExprType => tp.resultType.widenIfUnstable
case tp: TermRef if !tp.symbol.isStable => tp.underlying.widenIfUnstable
case tp: TermRef if !tp.symbol.isStableMember => tp.underlying.widenIfUnstable
case _ => this
}

Expand Down Expand Up @@ -2200,6 +2204,8 @@ object Types {
def underlyingRef: TermRef
}

/** The singleton type for path prefix#myDesignator.
*/
abstract case class TermRef(override val prefix: Type,
private var myDesignator: Designator)
extends NamedType with SingletonType with ImplicitRef {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ class TreePickler(pickler: TastyPickler) {
if (flags is Accessor) writeByte(FIELDaccessor)
if (flags is CaseAccessor) writeByte(CASEaccessor)
if (flags is DefaultParameterized) writeByte(DEFAULTparameterized)
if (flags is Stable) writeByte(STABLE)
if (flags is StableRealizable) writeByte(STABLE)
if (flags is Extension) writeByte(EXTENSION)
if (flags is ParamAccessor) writeByte(PARAMsetter)
assert(!(flags is Label))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ class TreeUnpickler(reader: TastyReader,
case CONTRAVARIANT => addFlag(Contravariant)
case SCALA2X => addFlag(Scala2x)
case DEFAULTparameterized => addFlag(DefaultParameterized)
case STABLE => addFlag(Stable)
case STABLE => addFlag(StableRealizable)
case EXTENSION => addFlag(Extension)
case PARAMsetter =>
addFlag(ParamAccessor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ object PickleBuffer {
LOCAL -> Local,
JAVA -> JavaDefined,
SYNTHETIC -> Synthetic,
STABLE -> Stable,
STABLE -> StableRealizable,
STATIC -> JavaStatic,
CASEACCESSOR -> CaseAccessor,
DEFAULTPARAM -> (DefaultParameterized, Trait),
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ object Completion {
!sym.is(Artifact) &&
(
(mode.is(Mode.Term) && sym.isTerm)
|| (mode.is(Mode.Type) && (sym.isType || sym.isStable))
|| (mode.is(Mode.Type) && (sym.isType || sym.isStableMember))
)

/**
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ object JavaParsers {
skipAhead()
accept(RBRACE)
}
ValDef(name.toTermName, enumType, unimplementedExpr).withMods(Modifiers(Flags.JavaEnum | Flags.Stable | Flags.JavaDefined | Flags.JavaStatic))
ValDef(name.toTermName, enumType, unimplementedExpr).withMods(Modifiers(Flags.JavaEnum | Flags.StableRealizable | Flags.JavaDefined | Flags.JavaStatic))
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
} else if (sym.is(Mutable, butNot = Accessor)) {
api.Var.of(sym.name.toString, apiAccess(sym), apiModifiers(sym),
apiAnnotations(sym).toArray, apiType(sym.info))
} else if (sym.isStable && !sym.isRealMethod) {
} else if (sym.isStableMember && !sym.isRealMethod) {
api.Val.of(sym.name.toString, apiAccess(sym), apiModifiers(sym),
apiAnnotations(sym).toArray, apiType(sym.info))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ trait FlagsOpsImpl extends scala.tasty.reflect.FlagsOps with CoreImpl {
def Contravariant: Flags = core.Flags.Contravariant
def Scala2X: Flags = core.Flags.Scala2x
def DefaultParameterized: Flags = core.Flags.DefaultParameterized
def Stable: Flags = core.Flags.Stable
def Stable: Flags = core.Flags.StableRealizable
def Param: Flags = core.Flags.Param
def ParamAccessor: Flags = core.Flags.ParamAccessor
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ object ExplicitOuter {
val deferredIfTrait = if (owner.is(Trait)) Deferred else EmptyFlags
val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags
newOuterSym(owner, cls, outerAccName(cls),
Final | Method | Stable | outerAccIfOwn | deferredIfTrait)
Final | Method | StableRealizable | outerAccIfOwn | deferredIfTrait)
}

private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Getters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Getters extends MiniPhase with SymTransformer {

var d1 =
if (d.isTerm && (d.is(Lazy) || d.owner.isClass) && d.info.isValueType && !noGetterNeeded) {
val maybeStable = if (d.isStable) Stable else EmptyFlags
val maybeStable = if (d.isStableMember) StableRealizable else EmptyFlags
d.copySymDenotation(
initFlags = d.flags | maybeStable | AccessorCreationFlags,
info = ExprType(d.info))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Memoize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
ctx.newSymbol(
owner = ctx.owner,
name = sym.name.asTermName.fieldName,
flags = Private | (if (sym is Stable) EmptyFlags else Mutable),
flags = Private | (if (sym is StableRealizable) EmptyFlags else Mutable),
info = fieldType,
coord = tree.span
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class ParamForwarding(thisPhase: DenotTransformer) {
val alias = inheritedAccessor(sym)
if (alias.exists) {
def forwarder(implicit ctx: Context) = {
sym.copySymDenotation(initFlags = sym.flags | Method | Stable, info = sym.info.ensureMethodic)
sym.copySymDenotation(initFlags = sym.flags | Method | StableRealizable, info = sym.info.ensureMethodic)
.installAfter(thisPhase)
val superAcc =
Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) =>
tpe.info match {
case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos)
case info: ConstantType if tpe.symbol.isStable => return info
case info: ConstantType if tpe.symbol.isStableMember => return info
case _ =>
}
case _ =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ class Namer { typer: Typer =>
cls.info = avoidPrivateLeaks(cls, cls.sourcePos)
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(Stable)
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ object PrepareInlineable {
sym.isTerm &&
(sym.is(AccessFlags) || sym.privateWithin.exists) &&
!sym.isContainedIn(inlineSym) &&
!(sym.isStable && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) &&
!(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) &&
!sym.isInlineMethod

def preTransform(tree: Tree)(implicit ctx: Context): Tree
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ object RefChecks {
intersectionIsEmpty(member.extendedOverriddenSymbols, other.extendedOverriddenSymbols)) {
overrideError("cannot override a concrete member without a third member that's overridden by both " +
"(this rule is designed to prevent ``accidental overrides'')")
} else if (other.isStable && !member.isStable) { // (1.4)
} else if (other.isStableMember && !member.isStableMember) { // (1.4)
overrideError("needs to be a stable, immutable value")
} else if (member.is(ModuleVal) && !other.isRealMethod && !other.is(Deferred | Lazy)) {
overrideError("may not override a concrete non-lazy value")
Expand Down