Semantic

object Semantic
class Object
trait Matchable
class Any

Type members

Classlikes

case class ArgInfo(value: Value, trace: Trace)

Utility definition used for better error-reporting of argument errors

Utility definition used for better error-reporting of argument errors

case class ByNameArg(tree: Tree)
object Cache

Cache used in fixed point computation

Cache used in fixed point computation

The analysis computes the least fixed point for the cache (see doc for ExprValueCache).

For the fixed point computation to terminate, we need to make sure that the domain of the cache, i.e. the key pair (Ref, Tree) is finite. As the code is finite, we only need to carefully design the abstract domain to be finitary.

We also need to make sure that the computing function (i.e. the abstract interpreter) is monotone. Error handling breaks monotonicity of the abstract interpreter, because when an error happens, we always return the bottom value Hot for an expression. It is not a threat for termination because when an error happens, we stop the fixed point computation at the end of the iteration where the error happens. Care must be paid to tests of errors, monotonicity will be broken if we simply ignore the test errors (See TryReporter).

Note: It's tempting to use location of trees as key. That should be avoided as a template may have the same location as its single statement body. Macros may also create incorrect locations.

object Call
case object Cold extends Value

An object with unknown initialization status

An object with unknown initialization status

case class Fun(expr: Tree, thisV: Ref, klass: ClassSymbol) extends Value

A function value

A function value

case object Hot extends Value

A transitively initialized object

A transitively initialized object

object NewExpr
case class Objekt(klass: ClassSymbol, fields: Map[Symbol, Value], outers: Map[ClassSymbol, Value])

The abstract object which stores value about its fields and immediate outers.

The abstract object which stores value about its fields and immediate outers.

Semantically it suffices to store the outer for klass. We cache other outers for performance reasons.

Note: Object is NOT a value.

object PolyFun
object Promoted
sealed abstract class Ref extends Value
case class RefSet(refs: List[Fun | Ref]) extends Value

A value which represents a set of addresses

A value which represents a set of addresses

It comes from if expressions.

object Reporter
Companion:
class
trait Reporter

Error reporting

Error reporting

Companion:
object
case class Task(value: ThisRef)
case class ThisRef(klass: ClassSymbol) extends Ref

A reference to the object under initialization pointed by this

A reference to the object under initialization pointed by this

object Trace
trait TryReporter extends Reporter

A TryReporter cannot be simply thrown away

A TryReporter cannot be simply thrown away

Either abort should be called or the errors be reported.

If errors are ignored and abort is not called, the monotonicity of the computation function is not guaranteed, thus termination of fixed-point computation becomes a problem.

sealed abstract class Value

Abstract values

Abstract values

Value = Hot | Cold | Warm | ThisRef | Fun | RefSet

           Cold
  ┌──────►  ▲  ◄────┐  ◄────┐
  │         │       │       │
  │         │       │       │
  |         │       │       │
  |         │       │       │

ThisRef Warm Fun RefSet │ ▲ ▲ ▲ │ │ │ │ | │ │ │ ▲ │ │ │ │ │ │ │ └─────────┴───────┴───────┘ Hot

The diagram above does not reflect relationship between RefSet and other values. RefSet represents a set of values which could be ThisRef, Warm or Fun. The following ordering applies for RefSet:

   R_a ⊑ R_b if R_a ⊆ R_b

   V ⊑ R if V ∈ R
case class Warm(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Ref

An object with all fields initialized but reaches objects under initialization

An object with all fields initialized but reaches objects under initialization

We need to restrict nesting levels of outer to finitize the domain.

class WorkList

Types

opaque type Arg

The state that threads through the interpreter

The state that threads through the interpreter

type Trace = Trace

Value members

Concrete methods

def addTask(thisRef: ThisRef)(using WorkList): Unit

Add a checking task to the work list

Add a checking task to the work list

inline def cache(using c: Cache): Cache
def cases(expr: Tree, thisV: Ref, klass: ClassSymbol): () ?=> Value

Handles the evaluation of different expressions

Handles the evaluation of different expressions

Note: Recursive call should go to eval instead of cases.

def cases(tp: Type, thisV: Ref, klass: ClassSymbol): () ?=> Value

Handle semantics of leaf nodes

Handle semantics of leaf nodes

def check()(using Cache, WorkList, Context): Unit

Perform check on the work list until it becomes empty

Perform check on the work list until it becomes empty

Should only be called once from the checker.

def checkTermUsage(tpt: Tree, thisV: Ref, klass: ClassSymbol): () ?=> Unit

Check that path in path-dependent types are initialized

Check that path in path-dependent types are initialized

This is intended to avoid type soundness issues in Dotty.

def eval(expr: Tree, thisV: Ref, klass: ClassSymbol, cacheResult: Boolean): () ?=> Value

Evaluate an expression with the given value for this in a given class klass

Evaluate an expression with the given value for this in a given class klass

Note that klass might be a super class of the object referred by thisV. The parameter klass is needed for this resolution. Consider the following code:

class A { A.this class B extends A { A.this } }

As can be seen above, the meaning of the expression A.this depends on where it is located.

This method only handles cache logic and delegates the work to cases.

The parameter cacheResult is used to reduce the size of the cache.

def eval(exprs: List[Tree], thisV: Ref, klass: ClassSymbol): () ?=> List[Value]

Evaluate a list of expressions

Evaluate a list of expressions

def evalArgs(args: List[Arg], thisV: Ref, klass: ClassSymbol): () ?=> List[ArgInfo]

Evaluate arguments of methods

Evaluate arguments of methods

inline def extendTrace[T](node: Tree)(using t: Trace)(op: Trace ?=> T): T
def init(tpl: Template, thisV: Ref, klass: ClassSymbol): () ?=> Value

Initialize part of an abstract object in klass of the inheritance chain

Initialize part of an abstract object in klass of the inheritance chain

def outerValue(tref: TypeRef, thisV: Ref, klass: ClassSymbol): () ?=> Value

Compute the outer value that correspond to tref.prefix

Compute the outer value that correspond to tref.prefix

inline def reporter(using r: Reporter): Reporter
def resolve(cls: ClassSymbol, sym: Symbol)(using Context): Symbol
def resolveOuterSelect(target: ClassSymbol, thisV: Value, hops: Int): () ?=> Value

Resolve outer select introduced during inlining.

Resolve outer select introduced during inlining.

See tpd.outerSelect and ElimOuterSelect.

def resolveSuper(cls: ClassSymbol, superType: Type, sym: Symbol)(using Context): Symbol
def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol): () ?=> Value

Resolve C.this that appear in klass

Resolve C.this that appear in klass

def trace(using t: Trace): Trace
def typeRefOf(tp: Type)(using Context): TypeRef
def withInitialState[T](work: (Cache, WorkList) ?=> T): T

Perform actions with initial checking state.

Perform actions with initial checking state.

Semantic.withInitialState {
   Semantic.addTask(...)
   ...
   Semantic.check()
}
inline def withTrace[T](t: Trace)(op: Trace ?=> T): T
inline def workList(using wl: WorkList): WorkList

Extensions

Extensions

extension (a: Value)
def join(b: Value): Value
def widenArg: () ?=> Value

Conservatively approximate the value with Cold or Hot

Conservatively approximate the value with Cold or Hot

extension (arg: Arg)
def tree: Tree
extension (ref: Ref)
def ensureFresh()(using Cache): Ref
def objekt: () ?=> Objekt
def updateField(field: Symbol, value: Value): () ?=> Unit

Update field value of the abstract object

Update field value of the abstract object

Invariant: fields are immutable and only set once

def updateOuter(klass: ClassSymbol, value: Value): () ?=> Unit

Update the immediate outer of the given klass of the abstract object

Update the immediate outer of the given klass of the abstract object

Invariant: outers are immutable and only set once

extension (ref: Ref)

Whether the object is fully assigned

Whether the object is fully assigned

It means all fields and outers are set. For performance, we don't check outers here, because Scala semantics ensure that they are always set before any user code in the constructor.

Note that isFullyFilled = true does not mean we can use the object freely, as its fields or outers may still reach uninitialized objects.

def nonInitFields(): () ?=> List[Symbol]
extension (ref: Ref)
def accessLocal(tmref: TermRef, klass: ClassSymbol): () ?=> Value
extension (symbol: Symbol)
extension (thisRef: ThisRef)
extension (value: Value)
def promote(msg: String): () ?=> Unit

Promotion of values to hot

Promotion of values to hot

extension (value: Value)
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean): () ?=> Value
def callConstructor(ctor: Symbol, args: List[ArgInfo]): () ?=> Value
def ensureHot(msg: String): () ?=> Value
def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): () ?=> Value

Handle a new expression new p.C where p is abstracted by value

Handle a new expression new p.C where p is abstracted by value

def select(field: Symbol, needResolve: Boolean): () ?=> Value
extension (value: Ref)

Can the method call on value be ignored?

Can the method call on value be ignored?

Note: assume overriding resolution has been performed.

extension (values: Seq[Value])
def join: Value
def widenArgs: () ?=> List[Value]
extension (warm: Warm)
def tryPromote(msg: String): () ?=> List[Error]

Try early promotion of warm objects

Try early promotion of warm objects

Promotion is expensive and should only be performed for small classes.

  1. for each concrete method m of the warm object: call the method and promote the result

  2. for each concrete field f of the warm object: promote the field value

If the object contains nested classes as members, the checker simply reports a warning to avoid expensive checks.

TODO: we need to revisit whether this is needed once we make the system more flexible in other dimentions: e.g. leak to methods or constructors, or use ownership for creating cold data structures.