diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 644b7620ae06..42108e10fb85 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -12,6 +12,7 @@ import typer.FrontEnd import collection.mutable.ListBuffer import util.Property import reporting.diagnostic.messages._ +import reporting.trace object desugar { import untpd._ @@ -886,7 +887,7 @@ object desugar { * @param enums The enumerators in the for expression * @param body The body of the for expression */ - def makeFor(mapName: TermName, flatMapName: TermName, enums: List[Tree], body: Tree): Tree = ctx.traceIndented(i"make for ${ForYield(enums, body)}", show = true) { + def makeFor(mapName: TermName, flatMapName: TermName, enums: List[Tree], body: Tree): Tree = trace(i"make for ${ForYield(enums, body)}", show = true) { /** Make a function value pat => body. * If pat is a var pattern id: T then this gives (id: T) => body diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 0861b7d870c2..0c5884509b52 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -7,6 +7,7 @@ import Flags._, Trees._, Types._, Contexts._ import Names._, StdNames._, NameOps._, Decorators._, Symbols._ import util.HashSet import typer.ConstFold +import reporting.trace trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => import TreeInfo._ @@ -578,7 +579,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * if no such path exists. * Pre: `sym` must have a position. */ - def defPath(sym: Symbol, root: Tree)(implicit ctx: Context): List[Tree] = ctx.debugTraceIndented(s"defpath($sym with position ${sym.pos}, ${root.show})") { + def defPath(sym: Symbol, root: Tree)(implicit ctx: Context): List[Tree] = trace.onDebug(s"defpath($sym with position ${sym.pos}, ${root.show})") { require(sym.pos.exists) object accum extends TreeAccumulator[List[Tree]] { def apply(x: List[Tree], tree: Tree)(implicit ctx: Context): List[Tree] = { diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 2e778a9db81a..153d2a5e52ac 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -144,8 +144,11 @@ object Config { */ final val simplifyApplications = true - /** Initial size of superId table */ - final val InitialSuperIdsSize = 4096 + /** If set, prints a trace of all symbol completions */ + final val showCompletions = false + + /** If set, enables tracing */ + final val tracingEnabled = false /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 52c10f628e44..b0936864c426 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -27,7 +27,6 @@ object Printers { val incremental: Printer = noPrinter val config: Printer = noPrinter val transforms: Printer = noPrinter - val completions: Printer = noPrinter val cyclicErrors: Printer = noPrinter val pickling: Printer = noPrinter val inlining: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index dbc35be9f6bf..df22daaf9e89 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -17,7 +17,8 @@ import util.SimpleIdentityMap import util.Stats import java.util.WeakHashMap import config.Config -import config.Printers.{completions, incremental, noPrinter} +import config.Printers.{incremental, noPrinter} +import reporting.trace trait SymDenotations { this: Context => import SymDenotations._ @@ -214,28 +215,31 @@ object SymDenotations { case _ => Some(myInfo) } - private def completeFrom(completer: LazyType)(implicit ctx: Context): Unit = { - if (completions ne noPrinter) { - completions.println(i"${" " * indent}completing ${if (isType) "type" else "val"} $name") + private def completeFrom(completer: LazyType)(implicit ctx: Context): Unit = + if (Config.showCompletions) { + println(i"${" " * indent}completing ${if (isType) "type" else "val"} $name") indent += 1 - } - if (myFlags is Touched) throw CyclicReference(this) - myFlags |= Touched - - // completions.println(s"completing ${this.debugString}") - try completer.complete(this)(ctx.withPhase(validFor.firstPhaseId)) - catch { - case ex: CyclicReference => - completions.println(s"error while completing ${this.debugString}") - throw ex - } - finally - if (completions ne noPrinter) { + + if (myFlags is Touched) throw CyclicReference(this) + myFlags |= Touched + + // completions.println(s"completing ${this.debugString}") + try completer.complete(this)(ctx.withPhase(validFor.firstPhaseId)) + catch { + case ex: CyclicReference => + println(s"error while completing ${this.debugString}") + throw ex + } + finally { indent -= 1 - completions.println(i"${" " * indent}completed $name in $owner") + println(i"${" " * indent}completed $name in $owner") } - // completions.println(s"completed ${this.debugString}") - } + } + else { + if (myFlags is Touched) throw CyclicReference(this) + myFlags |= Touched + completer.complete(this)(ctx.withPhase(validFor.firstPhaseId)) + } protected[dotc] def info_=(tp: Type) = { /* // DEBUG @@ -797,7 +801,10 @@ object SymDenotations { */ /** The class implementing this module, NoSymbol if not applicable. */ final def moduleClass(implicit ctx: Context): Symbol = { - def notFound = { completions.println(s"missing module class for $name: $myInfo"); NoSymbol } + def notFound = { + if (Config.showCompletions) println(s"missing module class for $name: $myInfo") + NoSymbol + } if (this is ModuleVal) myInfo match { case info: TypeRef => info.symbol @@ -1666,7 +1673,7 @@ object SymDenotations { } } - /*>|>*/ ctx.debugTraceIndented(s"$tp.baseType($this)") /*<|<*/ { + /*>|>*/ trace.onDebug(s"$tp.baseType($this)") /*<|<*/ { Stats.record("baseTypeOf") tp.stripTypeVar match { // @!!! dealias? case tp: CachedType => diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index d48f3515267a..895147f6065e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -18,6 +18,7 @@ import classfile.ClassfileParser import util.Stats import scala.util.control.NonFatal import ast.Trees._ +import reporting.trace object SymbolLoaders { /** A marker trait for a completer that replaces the original @@ -265,7 +266,7 @@ abstract class SymbolLoader extends LazyType { try { val start = currentTime if (ctx.settings.debugTrace.value) - ctx.doTraceIndented(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") { + trace(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") { doComplete(root) } else diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d7bc6ddd5835..d1b10dcdcab8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -12,6 +12,7 @@ import config.Printers.{typr, constr, subtyping, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ import scala.util.control.NonFatal +import reporting.trace /** Provides methods to compare types. */ @@ -105,7 +106,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { assert(isSatisfiable, constraint.show) } - protected def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) { + protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) { if (tp2 eq NoType) false else if (tp1 eq tp2) true else { @@ -948,7 +949,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * rebase both itself and the member info of `tp` on a freshly created skolem type. */ protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = - /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) /*<|<*/ { + /*>|>*/ trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) /*<|<*/ { val rinfo2 = tp2.refinedInfo // If the member is an abstract type, compare the member itself @@ -1159,7 +1160,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** Same as `isSameType` but also can be applied to overloaded TermRefs, where * two overloaded refs are the same if they have pairwise equal alternatives */ - def isSameRef(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSameRef($tp1, $tp2") { + def isSameRef(tp1: Type, tp2: Type): Boolean = trace(s"isSameRef($tp1, $tp2") { def isSubRef(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: TermRef if tp1.isOverloaded => tp1.alternatives forall (isSubRef(_, tp2)) @@ -1175,7 +1176,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } /** The greatest lower bound of two types */ - def glb(tp1: Type, tp2: Type): Type = /*>|>*/ ctx.traceIndented(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ { + def glb(tp1: Type, tp2: Type): Type = /*>|>*/ trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp2 else if (!tp2.exists) tp1 @@ -1224,7 +1225,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * @param canConstrain If true, new constraints might be added to simplify the lub. * @note We do not admit singleton types in or-types as lubs. */ - def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ { + def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ trace(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp1 else if (!tp2.exists) tp2 @@ -1383,7 +1384,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * * In these cases, a MergeError is thrown. */ - final def andType(tp1: Type, tp2: Type, erased: Boolean = ctx.erasedTypes) = ctx.traceIndented(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) { + final def andType(tp1: Type, tp2: Type, erased: Boolean = ctx.erasedTypes) = trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) { val t1 = distributeAnd(tp1, tp2) if (t1.exists) t1 else { diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9c65ab9b94af..262f0d5f35c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -16,6 +16,7 @@ import config.Config import util.Property import collection.mutable import ast.tpd._ +import reporting.trace trait TypeOps { this: Context => // TODO: Make standalone object. @@ -33,7 +34,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Map a `C.this` type to the right prefix. If the prefix is unstable, and * the current variance is <= 0, return a range. */ - def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ { + def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ trace.conditionally(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)", show = true) /*<|<*/ { if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) tp else pre match { @@ -49,7 +50,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } - /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG + /*>|>*/ trace.conditionally(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG // All cases except for ThisType are the same as in Map. Inlined for performance // TODO: generalize the inlining trick? tp match { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c013df88ce82..8c6c641a5784 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -830,7 +830,7 @@ object Types { } /** The basetype of this type with given class symbol, NoType if `base` is not a class. */ - final def baseType(base: Symbol)(implicit ctx: Context): Type = /*ctx.traceIndented(s"$this baseType $base")*/ /*>|>*/ track("base type") /*<|<*/ { + final def baseType(base: Symbol)(implicit ctx: Context): Type = /*trace(s"$this baseType $base")*/ /*>|>*/ track("base type") /*<|<*/ { base.denot match { case classd: ClassDenotation => classd.baseTypeOf(this) case _ => NoType diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 19e79f36d4a9..7712b5e6af7c 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -28,6 +28,7 @@ import classfile.ClassfileParser import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer import scala.annotation.switch +import reporting.trace object Scala2Unpickler { @@ -571,7 +572,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } else { assert(denot.is(ParamAccessor) || denot.isSuperAccessor, denot) def disambiguate(alt: Symbol) = { // !!! DEBUG - ctx.debugTraceIndented(s"disambiguating ${denot.info} =:= ${denot.owner.thisType.memberInfo(alt)} ${denot.owner}") { + trace.onDebug(s"disambiguating ${denot.info} =:= ${denot.owner.thisType.memberInfo(alt)} ${denot.owner}") { denot.info matches denot.owner.thisType.memberInfo(alt) } } diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 43ffb3328a17..708a38512b32 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -6,7 +6,6 @@ import core.Contexts._ import util.{SourcePosition, NoSourcePosition} import core.Decorators.PhaseListDecorator import collection.mutable -import config.Printers import java.lang.System.currentTimeMillis import core.Mode import dotty.tools.dotc.core.Symbols.Symbol @@ -118,63 +117,13 @@ trait Reporting { this: Context => def informProgress(msg: => String) = inform("[" + msg + "]") - def trace[T](msg: => String)(value: T) = { + def logWith[T](msg: => String)(value: T) = { log(msg + " " + value) value } def debugwarn(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.debug.value) warning(msg, pos) - - @inline - def debugTraceIndented[TD](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TD): TD = - conditionalTraceIndented(this.settings.debugTrace.value, question, printer, show)(op) - - @inline - def conditionalTraceIndented[TC](cond: Boolean, question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TC): TC = - if (cond) traceIndented[TC](question, printer, show)(op) - else op - - @inline - def traceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = - if (printer eq config.Printers.noPrinter) op - else doTraceIndented[T](question, printer, show)(op) - - private def doTraceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = { - def resStr(res: Any): String = res match { - case res: printing.Showable if show => res.show - case _ => String.valueOf(res) - } - // Avoid evaluating question multiple time, since each evaluation - // may cause some extra logging output. - lazy val q: String = question - doTraceIndented[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) - } - - def doTraceIndented[T](leading: => String, trailing: Any => String)(op: => T): T = - if (ctx.mode.is(Mode.Printing)) op - else { - var finalized = false - var logctx = this - while (logctx.reporter.isInstanceOf[StoreReporter]) logctx = logctx.outer - def finalize(result: Any, note: String) = - if (!finalized) { - base.indent -= 1 - logctx.log(s"${base.indentTab * base.indent}${trailing(result)}$note") - finalized = true - } - try { - logctx.log(s"${base.indentTab * base.indent}$leading") - base.indent += 1 - val res = op - finalize(res, "") - res - } catch { - case ex: Throwable => - finalize("", s" (with exception $ex)") - throw ex - } - } } /** diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala new file mode 100644 index 000000000000..6e10be9dc7f2 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -0,0 +1,74 @@ +package dotty.tools +package dotc +package reporting + +import core.Contexts.Context +import config.Config +import config.Printers +import core.Mode + +object trace { + + @inline + def onDebug[TD](question: => String)(op: => TD)(implicit ctx: Context): TD = + conditionally(ctx.settings.debugTrace.value, question, false)(op) + + @inline + def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC = + if (Config.tracingEnabled && cond) apply[TC](question, Printers.default, show)(op) + else op + + @inline + def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = + if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op + else doTrace[T](question, printer, show)(op) + + @inline + def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = + apply[T](question, printer, false)(op) + + @inline + def apply[T](question: => String, show: Boolean)(op: => T)(implicit ctx: Context): T = + apply[T](question, Printers.default, show)(op) + + @inline + def apply[T](question: => String)(op: => T)(implicit ctx: Context): T = + apply[T](question, Printers.default, false)(op) + + private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false) + (op: => T)(implicit ctx: Context): T = { + def resStr(res: Any): String = res match { + case res: printing.Showable if show => res.show + case _ => String.valueOf(res) + } + // Avoid evaluating question multiple time, since each evaluation + // may cause some extra logging output. + lazy val q: String = question + apply[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) + } + + def apply[T](leading: => String, trailing: Any => String)(op: => T)(implicit ctx: Context): T = + if (ctx.mode.is(Mode.Printing)) op + else { + var finalized = false + var logctx = ctx + while (logctx.reporter.isInstanceOf[StoreReporter]) logctx = logctx.outer + def finalize(result: Any, note: String) = + if (!finalized) { + ctx.base.indent -= 1 + logctx.log(s"${ctx.base.indentTab * ctx.base.indent}${trailing(result)}$note") + finalized = true + } + try { + logctx.log(s"${ctx.base.indentTab * ctx.base.indent}$leading") + ctx.base.indent += 1 + val res = op + finalize(res, "") + res + } catch { + case ex: Throwable => + finalize("", s" (with exception $ex)") + throw ex + } + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 5e77ab9609a0..7545b36246a4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -29,6 +29,7 @@ import TypeUtils._ import ExplicitOuter._ import core.Mode import core.PhantomErasure +import reporting.trace class Erasure extends Phase with DenotTransformer { thisTransformer => @@ -175,7 +176,7 @@ object Erasure { (if (isPureExpr(tree)) const else Block(tree :: Nil, const)) .withPos(tree.pos) - final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { + final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = trace(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(tycon, _) => New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType? @@ -195,7 +196,7 @@ object Erasure { } } - def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { + def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = trace(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { pt match { case ErasedValueType(tycon, underlying) => def unboxedTree(t: Tree) = @@ -239,7 +240,7 @@ object Erasure { * Casts from and to ErasedValueType are special, see the explanation * in ExtensionMethods#transform. */ - def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"cast ${tree.tpe.widen} --> $pt", show = true) { + def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = trace(i"cast ${tree.tpe.widen} --> $pt", show = true) { def wrap(tycon: TypeRef) = ref(u2evt(tycon.typeSymbol.asClass)).appliedTo(tree) def unwrap(tycon: TypeRef) = @@ -662,7 +663,7 @@ object Erasure { } override def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - ctx.traceIndented(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { + trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { assert(ctx.phase == ctx.erasurePhase.next, ctx.phase) if (tree.isEmpty) tree else if (ctx.mode is Mode.Pattern) tree // TODO: replace with assertion once pattern matcher is active diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 44385845e3ec..efe99b4f6062 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -32,8 +32,8 @@ import StdNames._ * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types * - drops branches of ifs using the rules - * if (true) A else B --> A - * if (false) A else B --> B + * if (true) A else B ==> A + * if (false) A else B ==> B */ class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => import ast.tpd._ @@ -203,7 +203,7 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota constToLiteral(tree) override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = - constToLiteral(tree) + constToLiteral(foldCondition(tree)) override def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo) = constToLiteral(tree) @@ -217,6 +217,27 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota case _ => tree } + /** Perform one of the following simplification if applicable: + * + * true && y ==> y + * false && y ==> false + * true || y ==> true + * false || y ==> y + */ + private def foldCondition(tree: Apply)(implicit ctx: Context) = tree.fun match { + case Select(x @ Literal(Constant(c: Boolean)), op) => + tree.args match { + case y :: Nil if y.tpe.widen.isRef(defn.BooleanClass) => + op match { + case nme.ZAND => if (c) y else x + case nme.ZOR => if (c) x else y + case _ => tree + } + case _ => tree + } + case _ => tree + } + // invariants: all modules have companion objects // all types are TypeTrees // all this types are explicit diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala index b9218163666b..f500583d172a 100644 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala @@ -12,6 +12,7 @@ import Decorators._ import DenotTransformers._ import core.StdNames.nme import ast.Trees._ +import reporting.trace /** Abstract base class of ByNameClosures and ElimByName, factoring out the * common functionality to transform arguments of by-name parameters. @@ -35,7 +36,7 @@ abstract class TransformByNameApply extends MiniPhaseTransform { thisTransformer def mkByNameClosure(arg: Tree, argType: Type)(implicit ctx: Context): Tree = unsupported(i"mkClosure($arg)") override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = - ctx.traceIndented(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { + trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { case formalExpr: ExprType => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala index b316176259de..fe4e4e5df069 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -17,6 +17,7 @@ import dotty.tools.dotc.util.DotClass import scala.annotation.tailrec import config.Printers.transforms import scala.util.control.NonFatal +import reporting.trace object TreeTransforms { import tpd._ @@ -1196,7 +1197,7 @@ object TreeTransforms { private[this] var crashingTree: Tree = EmptyTree - def transform(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show} at ${ctx.phase}", transforms, show = true) { + def transform(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show} at ${ctx.phase}", transforms, show = true) { try if (cur < info.transformers.length) { util.Stats.record("TreeTransform.transform") diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index b5a1b4170421..c40cc7b213f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -10,6 +10,7 @@ import ValueClasses._ import SymUtils._ import core.Flags._ import util.Positions._ +import reporting.trace /** This transform normalizes type tests and type casts, @@ -26,7 +27,7 @@ import util.Positions._ object TypeTestsCasts { import ast.tpd._ - def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show}", show = true) { + def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) { tree.fun match { case fun @ Select(expr, selector) => val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a0e8b8c6972e..3ac531eed049 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -31,6 +31,7 @@ import TypeApplications._ import language.implicitConversions import reporting.diagnostic.Message +import reporting.trace import Constants.{Constant, IntTag, LongTag} import scala.collection.mutable.ListBuffer @@ -1045,7 +1046,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * - The nesting levels of A1 and A2 are the same, and A1's owner derives from A2's owner * - A1's type is more specific than A2's type. */ - def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { + def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { trace(i"isAsGood($alt1, $alt2)", overload) { assert(alt1 ne alt2) @@ -1072,7 +1073,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * b. as specific as a member of any other type `tp2` if `tp1` is compatible * with `tp2`. */ - def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = ctx.traceIndented(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { + def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { case tp1: MethodType => // (1) val formals1 = if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bd1cca03f77e..14da2a95a23a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -33,6 +33,7 @@ import util.Property import config.Config import config.Printers.{implicits, implicitsDetailed, typr} import collection.mutable +import reporting.trace /** Implicit resolution */ object Implicits { @@ -61,7 +62,7 @@ object Implicits { /** Return those references in `refs` that are compatible with type `pt`. */ protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") { - def refMatches(ref: TermRef)(implicit ctx: Context) = /*ctx.traceIndented(i"refMatches $ref $pt")*/ { + def refMatches(ref: TermRef)(implicit ctx: Context) = /*trace(i"refMatches $ref $pt")*/ { def discardForView(tpw: Type, argType: Type): Boolean = tpw match { case mt: MethodType => @@ -156,7 +157,7 @@ object Implicits { /** The candidates that are eligible for expected type `tp` */ lazy val eligible: List[Candidate] = /*>|>*/ track("eligible in tpe") /*<|<*/ { - /*>|>*/ ctx.traceIndented(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ { + /*>|>*/ trace(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ { if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length) filterMatching(tp) } @@ -222,7 +223,7 @@ object Implicits { } } - private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { + private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { if (monitored) record(s"check eligible refs in ctx", refs.length) val ownEligible = filterMatching(tp) if (isOuterMost) ownEligible @@ -406,7 +407,7 @@ trait ImplicitRunInfo { self: RunInfo => // todo: compute implicits directly, without going via companionRefs? def collectCompanions(tp: Type): TermRefSet = track("computeImplicitScope") { - ctx.traceIndented(i"collectCompanions($tp)", implicits) { + trace(i"collectCompanions($tp)", implicits) { def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match { case Some(is) => @@ -708,7 +709,7 @@ trait Implicits { self: Typer => if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}") val prevConstr = ctx.typerState.constraint - ctx.traceIndented(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { + trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { assert(!pt.isInstanceOf[ExprType]) val isearch = if (ctx.settings.explainImplicits.value) new ExplainedImplicitSearch(pt, argument, pos) @@ -779,7 +780,7 @@ trait Implicits { self: Typer => val constr = ctx.typerState.constraint /** Try to typecheck an implicit reference */ - def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { + def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { assert(constr eq ctx.typerState.constraint) val ref = cand.ref var generated: Tree = tpd.ref(ref).withPos(pos.startPos) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 00c60b5e3a4b..e174c9a11b19 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -25,6 +25,7 @@ import config.Printers.inlining import ErrorReporting.errorTree import collection.mutable import transform.TypeUtils._ +import reporting.trace object Inliner { import tpd._ @@ -486,7 +487,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx) val expansion = inliner(rhs.withPos(call.pos)) - ctx.traceIndented(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { + trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index d9d852b63198..61c57858f450 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -16,7 +16,7 @@ import annotation.tailrec import ErrorReporting._ import tpd.ListOfTreeDecorator import config.Config -import config.Printers.{typr, completions, noPrinter} +import config.Printers.{typr, noPrinter} import Annotations._ import Inferencing._ import transform.ValueClasses._ @@ -746,14 +746,13 @@ class Namer { typer: Typer => } final override def complete(denot: SymDenotation)(implicit ctx: Context) = { - if (completions != noPrinter && ctx.typerState != this.ctx.typerState) { - completions.println(completions.getClass.toString) + if (Config.showCompletions && ctx.typerState != this.ctx.typerState) { def levels(c: Context): Int = if (c.typerState eq this.ctx.typerState) 0 else if (c.typerState == null) -1 else if (c.outer.typerState == c.typerState) levels(c.outer) else levels(c.outer) + 1 - completions.println(s"!!!completing ${denot.symbol.showLocated} in buried typerState, gap = ${levels(ctx)}") + println(s"!!!completing ${denot.symbol.showLocated} in buried typerState, gap = ${levels(ctx)}") } assert(ctx.runId == creationContext.runId, "completing $denot in wrong run ${ctx.runId}, was created in ${creationContext.runId}") completeInCreationContext(denot) @@ -1062,7 +1061,7 @@ class Namer { typer: Typer => // Approximate a type `tp` with a type that does not contain skolem types. val deskolemize = new ApproximatingTypeMap { - def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ { + def apply(tp: Type) = /*trace(i"deskolemize($tp) at $variance", show = true)*/ { tp match { case tp: SkolemType => range(tp.bottomType, atVariance(1)(apply(tp.info))) case _ => mapOver(tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bc34551a3739..095ccd29bdfe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -38,6 +38,7 @@ import config.Printers.{gadts, typr} import rewrite.Rewrites.patch import NavigateAST._ import transform.SymUtils._ +import reporting.trace import language.implicitConversions import printing.SyntaxHighlighting._ @@ -899,7 +900,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * which appear with variances +1 and -1 (in different * places) be considered as well? */ - val gadtSyms: Set[Symbol] = ctx.traceIndented(i"GADT syms of $selType", gadts) { + val gadtSyms: Set[Symbol] = trace(i"GADT syms of $selType", gadts) { val accu = new TypeAccumulator[Set[Symbol]] { def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { val tsyms1 = t match { @@ -1688,7 +1689,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit typed(ifun, pt) } - def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { + def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ trace(i"typing $tree", typr, show = true) /*<|<*/ { record(s"typed $getClass") record("typed total") assertPositioned(tree) @@ -1839,7 +1840,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** If this tree is a select node `qual.name`, try to insert an implicit conversion * `c` around `qual` so that `c(qual).name` conforms to `pt`. */ - def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = ctx.traceIndented(i"try insert impl on qualifier $tree $pt") { + def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") { tree match { case Select(qual, name) => val qualProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) @@ -1854,7 +1855,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { - /*>|>*/ ctx.traceIndented(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { + /*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { if (tree.isDef) interpolateUndetVars(tree, tree.symbol) else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol) tree.overwriteType(tree.tpe.simplified) diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index aec4fe128272..39d635863426 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -9,6 +9,7 @@ import Variances._ import util.Positions._ import rewrite.Rewrites.patch import config.Printers.variances +import reporting.trace /** Provides `check` method to check that all top-level definitions * in tree are variance correct. Does not recurse inside methods. @@ -38,7 +39,7 @@ class VarianceChecker()(implicit ctx: Context) { * The search proceeds from `base` to the owner of `tvar`. * Initially the state is covariant, but it might change along the search. */ - def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = /*ctx.traceIndented(i"relative variance of $tvar wrt $base, so far: $v")*/ { + def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = /*trace(i"relative variance of $tvar wrt $base, so far: $v")*/ { if (base == tvar.owner) v else if ((base is Param) && base.owner.isTerm) relativeVariance(tvar, paramOuter(base.owner), flip(v)) @@ -80,7 +81,7 @@ class VarianceChecker()(implicit ctx: Context) { * explicitly (their TypeDefs will be passed here.) For MethodTypes, the * same is true of the parameters (ValDefs). */ - def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = ctx.traceIndented(s"variance checking $tp of $base at $variance", variances) { + def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = trace(s"variance checking $tp of $base at $variance", variances) { if (status.isDefined) status else tp match { case tp: TypeRef => diff --git a/compiler/test/dotty/tools/dotc/ConstantFoldingTests.scala b/compiler/test/dotty/tools/dotc/ConstantFoldingTests.scala new file mode 100644 index 000000000000..176d31559291 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ConstantFoldingTests.scala @@ -0,0 +1,86 @@ +package dotty.tools.dotc + +import org.junit.Assert._ +import org.junit.Test +import dotty.tools.backend.jvm._ +import dotty.tools.dotc.config.CompilerCommand +import dotty.tools.dotc.core.Contexts.FreshContext +import scala.tools.asm.tree.MethodNode + +import scala.collection.JavaConverters._ + +class ConstantFoldingTests extends DottyBytecodeTest { + + val conditionSrc = """class Test { + def someCondition: Boolean = ??? + final val t = true + final val f = false + + def whenTrue = + println("hi") + def whenFalse = + () + def whenCond = + if (someCondition) + println("hi") + + def reduceToTrue1 = + if (t) + println("hi") + def reduceToTrue2 = + if (t || someCondition) + println("hi") + def reduceToTrue3 = + if (f || t || someCondition) + println("hi") + + def reduceToFalse1 = + if (f) + println("hi") + def reduceToFalse2 = + if (f && someCondition) + println("hi") + def reduceToFalse3 = + if (t && f && someCondition) + println("hi") + + def reduceToCond1 = + if (t && someCondition) + println("hi") + def reduceToCond2 = + if (f || someCondition) + println("hi") + def reduceToCond3 = + if ((t && f) || someCondition) + println("hi") +} +""" + + @Test def constantFoldConditions: Unit = { + import ASMConverters._ + + checkBCode(conditionSrc) { dir => + val clsIn = dir.lookupName(s"Test.class", directory = false).input + val methods = loadClassNode(clsIn).methods.asScala + + val whenTrue = methods.find(_.name == "whenTrue").get + val whenFalse = methods.find(_.name == "whenFalse").get + val whenCond = methods.find(_.name == "whenCond").get + + val reduceToTrue = methods.filter(_.name.startsWith("reduceToTrue")) + val reduceToFalse = methods.filter(_.name.startsWith("reduceToFalse")) + val reduceToCond = methods.filter(_.name.startsWith("reduceToCond")) + + def compare(expected: MethodNode, actual: MethodNode) = { + val expectedInstrs = instructionsFromMethod(expected) + val actualInstrs = instructionsFromMethod(actual) + val diff = diffInstructions(expectedInstrs, actualInstrs) + assert(expectedInstrs == actualInstrs, + s"Different bytecode between ${expected.name} and ${actual.name}\n$diff") + } + reduceToTrue.foreach(compare(whenTrue, _)) + reduceToFalse.foreach(compare(whenFalse, _)) + reduceToCond.foreach(compare(whenCond, _)) + } + } +} diff --git a/tests/run/i2916.scala b/tests/run/i2916.scala index 3b36a00524f2..49e64d6e63da 100644 --- a/tests/run/i2916.scala +++ b/tests/run/i2916.scala @@ -1,7 +1,7 @@ object Test { def p(x: Int) = { println(x); x } def foo(x1: Int, x2: Int, x3: Int, x4: Int = p(4), x5: Int = p(5)) = 1 - def traceIndented(x1: Int, x2: Int = p(2), x3: Int = p(3), x4: Int = p(4)) = () + def trace(x1: Int, x2: Int = p(2), x3: Int = p(3), x4: Int = p(4)) = () def main(args: Array[String]) = { foo(p(1), p(2), p(3)) // 1 2 3 4 5 @@ -25,13 +25,13 @@ object Test { { println(0); Test }.foo(p(1), x3 = p(3), x4 = p(4), x2 = p(2)) // 0 1 3 4 2 5 println() - traceIndented(p(1), x3 = p(3)) // 1 3 2 4 + trace(p(1), x3 = p(3)) // 1 3 2 4 println() - traceIndented(1, x3 = p(3)) // 3 2 4 + trace(1, x3 = p(3)) // 3 2 4 println() - traceIndented(p(1), x3 = 3) // 1 2 4 + trace(p(1), x3 = 3) // 1 2 4 } } \ No newline at end of file