diff --git a/AGENTS.md b/AGENTS.md index ff1f3c52ba..07d0cf536a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,6 +74,7 @@ exactly where the mutable state is supposed to be defined and used. Never use default arguments in core business logic. Default arguments should be reserved for user-facing APIs. +Do NOT encode default arguments through overloading to skirt this rule. Keep it DRY: if you find yourself copying and pasting code, consider refactoring it into a reusable function or class. @@ -94,6 +95,10 @@ avoid vague claims about where a "lifecycle" or "responsibility" belongs. If his or a rejected alternative matters, include enough context to explain the tradeoff after commits are squashed. +Do NOT include comments that simply describe what the code is doing +without adding any additional insight, or comments that talk about +parts of the system that are not directly related to the code being commented on. + ## User-Facing Diagnostics diff --git a/doc/reference.md b/doc/reference.md index 9a323cb44e..2878e79531 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -27,6 +27,7 @@ which is an evolving language that has not stabilized just yet. 17. [Indentation and Block Syntax](#17-indentation-and-block-syntax) 18. [Miscellaneous](#18-miscellaneous) 19. [Built-in Types and Prelude](#19-built-in-types-and-prelude) +20. [Compiler Debugging](#20-compiler-debugging) --- @@ -2035,6 +2036,128 @@ parenthesizedIf(x, cond) // wrap in parens if cond --- +## 20. Compiler Debugging + +The compiler can report what it does in each of its phases, and show the intermediate programs it produces. +This is configured in the source code, either for a whole file with the `#dbg` directive, +or for individual definitions with the `@dbg` annotation. +The output is meant for compiler developers: its format is not stable and may change at any time. + +### Debugging Aspects + +Each argument of `#dbg` or `@dbg` enables or disables one *aspect*. +The following aspects trace a phase of the compiler: + +| Aspect | Traces | +|---|---| +| `elaboration` | elaboration of syntax trees into terms | +| `lowering` | lowering of terms into the intermediate representation (IR) | +| `optimizations` | the optimization passes run on the IR | + +With the new name resolution (`#lang(0.3.x)`), names are resolved during elaboration, +so `elaboration` traces name resolution as well. + +Parser tracing is available through the diff-test command `:dp`. +Source directives are read after parsing and cannot enable parser tracing. + +The following aspects display a program representation, without tracing: + +| Aspect | Displays | +|---|---| +| `showParsedTree` | the syntax tree produced by the parser | +| `showElaboratedTree` | the elaborated term, as a tree | +| `showElaboratedTerm` | the elaborated term, in readable form | +| `showLoweredTree` | the IR produced by lowering, as a tree | +| `showIR` | the IR produced by lowering, in readable form | +| `showOptimizedIR` | the IR after optimizations, in readable form | + +Finally, `showUids` (enabled by default) controls whether traces show the unique identifiers of symbols. +Disabling it makes traces easier to compare across compiler runs. +UIDs are local to a compilation unit, so imported symbols use disambiguated names without numeric UIDs. + +### Syntax + +An aspect is enabled by writing its name as a quoted identifier, +or enabled or disabled by giving it a Boolean value: + +```mlscript +#dbg('lowering) // enable an aspect +#dbg(elaboration: true, showIR: true) // enable aspects explicitly +#dbg(showUids: false) // disable an aspect +``` + +Unknown aspects and non-Boolean values are reported as errors. + +### Output Destination + +Debugging output is written to the standard output by default, which `out: StdIO` selects explicitly. +The `out: File(...)` argument writes it to a file instead: + +```mlscript +#dbg('lowering, out: File("lowering.log")) +``` + +As for imports, a relative `File` path is resolved against the directory of the file +in which the `#dbg` directive or `@dbg` annotation is written, and an absolute path is used as is. +The first time the compiler writes to a file during a compilation session, it replaces the file's previous contents; +it then appends all later output, including the output of other files compiled in the same session, +such as imported files. +Output from concurrent compilations to the same file may be interleaved. + +Any other destination is reported as an error. + +### The `#dbg` Directive + +The `#dbg` directive configures debugging for the file in which it appears, and must appear at its top level: + +```mlscript +#dbg('elaboration, 'showOptimizedIR, out: File("debug.log")) + +fun f(x) = x + 1 +``` + +Several `#dbg` directives may set different aspects. +Currently, debugging settings apply to the whole file regardless of where the directive appears, +so if several directives set the same aspect, the last one takes effect. + +### The `@dbg` Annotation + +The `@dbg` annotation takes the same arguments as `#dbg`, +but only applies them to the definition it annotates. +It can annotate functions, methods, values, classes, modules, and objects: + +```mlscript +@dbg('elaboration, 'showIR, showUids: false) +fun selected(x) = x + 1 + +fun unrelated(x) = x // not traced, not displayed +``` + +Annotations combine with the file's settings: +for example, `@dbg(lowering: false)` stops a lowering trace enabled by `#dbg` for the annotated definition. +They also apply to the definitions nested in the annotated definition, +which can override them with their own `@dbg` annotations: + +```mlscript +@dbg('lowering, out: File("lowering.log")) +fun traced() = + fun helper() = 1 // traced to lowering.log + @dbg(lowering: false) fun muted() = 2 // not traced + helper() + muted() +``` + +The statements that follow an annotated definition are not affected by its annotation. +The lowering trace of an annotated class also covers the evaluation of its superclass, if any, +and its companion module, if any, which is compiled together with the class. + +The `optimizations` aspect cannot be selected per definition, as optimization passes process whole files. +Setting it in `@dbg` produces a warning and has no effect; use `#dbg` instead. + +Currently, the destination selected by an enclosing `@dbg` annotation only applies to traces: +the displays requested by the annotation of a nested definition use the file's destination. + +--- + ## Appendix: Grammar Summary ``` diff --git a/hkmc2/js/src/main/scala/hkmc2/io/InMemoryFileSystem.scala b/hkmc2/js/src/main/scala/hkmc2/io/InMemoryFileSystem.scala index 668c76ac2d..355924c30d 100644 --- a/hkmc2/js/src/main/scala/hkmc2/io/InMemoryFileSystem.scala +++ b/hkmc2/js/src/main/scala/hkmc2/io/InMemoryFileSystem.scala @@ -7,17 +7,22 @@ import scala.scalajs.js.annotation.JSExportTopLevel /** * In-memory file system for testing and web compiler. Stores files as a map - * from normalized path strings to content strings. + * from normalized path strings to buffers, so repeated appends do not copy the whole file. */ class InMemoryFileSystem(initialFiles: Map[String, String]) extends FileSystem: - private val files: MutMap[String, (Int, String)] = MutMap.from: - initialFiles.map { case (k, v) => (Path(k).toString, (0, v)) } + private val files: MutMap[String, (Int, StringBuilder)] = MutMap.from: + initialFiles.map { case (k, v) => (Path(k).toString, (0, new StringBuilder(v))) } def read(path: Path): String = read(path.toString) def write(path: Path, content: String): Unit = write(path.toString, content) + def append(path: Path, content: String): Unit = + files.updateWith(path.toString): + case Some((ts, buffer)) => Some((ts + 1, buffer.append(content))) + case None => Some((0, new StringBuilder(content))) + def exists(path: Path): Bool = files.contains(path.toString) def getLastChangedTimestamp(path: Path): Long = @@ -28,20 +33,20 @@ class InMemoryFileSystem(initialFiles: Map[String, String]) extends FileSystem: @JSExport("write") def write(path: Str, content: Str): Unit = files.updateWith(Path(path).toString): - case Some((ts, _)) => Some((ts + 1, content)) - case None => Some((0, content)) + case Some((ts, _)) => Some((ts + 1, new StringBuilder(content))) + case None => Some((0, new StringBuilder(content))) @JSExport("read") def read(path: Str): Str = val normalizedPath = Path(path) files.getOrElse(normalizedPath.toString, - throw new FileSystem.FileNotFoundException(normalizedPath))._2 + throw new FileSystem.FileNotFoundException(normalizedPath))._2.result() @JSExport("list") - def list: js.Array[Str] = allFiles.keys.toJSArray + def list: js.Array[Str] = files.keys.toJSArray /** Get all files (for debugging) */ - def allFiles: Map[String, String] = files.view.mapValues(_._2).toMap + def allFiles: Map[String, String] = files.view.mapValues(_._2.result()).toMap object InMemoryFileSystem: /** Create an empty in-memory file system. */ diff --git a/hkmc2/js/src/main/scala/hkmc2/io/node/NodeFileSystem.scala b/hkmc2/js/src/main/scala/hkmc2/io/node/NodeFileSystem.scala index 862157b1ca..cf38594a02 100644 --- a/hkmc2/js/src/main/scala/hkmc2/io/node/NodeFileSystem.scala +++ b/hkmc2/js/src/main/scala/hkmc2/io/node/NodeFileSystem.scala @@ -17,6 +17,9 @@ private[io] class NodeFileSystem extends FileSystem: def write(path: Path, content: String): Unit = fs.writeFileSync(path.toString, content) + def append(path: Path, content: String): Unit = + fs.appendFileSync(path.toString, content) + def exists(path: Path): Bool = fs.existsSync(path.toString) diff --git a/hkmc2/js/src/main/scala/hkmc2/io/node/package.scala b/hkmc2/js/src/main/scala/hkmc2/io/node/package.scala index e62dadc9ad..0397d2f554 100644 --- a/hkmc2/js/src/main/scala/hkmc2/io/node/package.scala +++ b/hkmc2/js/src/main/scala/hkmc2/io/node/package.scala @@ -19,6 +19,7 @@ package object node: object fs extends js.Object: def readFileSync(path: Str, encoding: Str): Str = js.native def writeFileSync(path: Str, data: Str): Unit = js.native + def appendFileSync(path: Str, data: Str): Unit = js.native def readdirSync(path: Str): js.Array[Str] = js.native def existsSync(path: Str): Bool = js.native def lstatSync(path: Str): Stats = js.native diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 024a6a2d4e..8ff8c08733 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -252,8 +252,7 @@ class CompilerTest extends AnyFunSuite: compiler.compile("/B.mls") given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent given Raise = diagnostic => fail(diagnostic.toString) val prelude = cctx.getPrelude(paths.preludeFile).ctx val artifact = cctx.getElaboratedBlock(Path("/A.mls"), prelude) @@ -275,8 +274,7 @@ class CompilerTest extends AnyFunSuite: val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent given Raise = diagnostic => fail(diagnostic.toString) val prelude = cctx.getPrelude(paths.preludeFile).ctx val map = prelude.builtins.Array.defn.get.body.members("map").asTrm.get.defn.get @@ -316,8 +314,7 @@ class CompilerTest extends AnyFunSuite: val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent given Raise = diagnostic => fail(diagnostic.toString) fs.write("/Generic.mls", """#lang(0.3.x, strictResolution: true) |module Generic with @@ -353,8 +350,7 @@ class CompilerTest extends AnyFunSuite: val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent given Raise = diagnostic => fail(diagnostic.toString) fs.write("/Types.mls", """#lang(0.3.x, strictResolution: true) |module Types with @@ -391,8 +387,7 @@ class CompilerTest extends AnyFunSuite: val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent given Raise = diagnostic => fail(diagnostic.toString) val prelude = cctx.getPrelude(paths.preludeFile).ctx val parameter = prelude.builtins.Array.defn.get.tparams.head.sym @@ -425,8 +420,7 @@ class CompilerTest extends AnyFunSuite: val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace = false + given TL = TraceLogger.silent val errors = scala.collection.mutable.ArrayBuffer.empty[Diagnostic] given Raise = errors += _ fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false) diff --git a/hkmc2/js/src/test/scala/hkmc2/io/VirtualPathTests.scala b/hkmc2/js/src/test/scala/hkmc2/io/VirtualPathTests.scala index 3015a2fa58..3043058630 100644 --- a/hkmc2/js/src/test/scala/hkmc2/io/VirtualPathTests.scala +++ b/hkmc2/js/src/test/scala/hkmc2/io/VirtualPathTests.scala @@ -170,3 +170,16 @@ class VirtualPathTests extends AnyFunSuite: assert(fs.read("/a/b.mls") == "initial") fs.write("/a/c/../b.mls", "updated") assert(fs.read("/a/./b.mls") == "updated") + + test("in-memory append preserves snapshots and updates normalized paths"): + val fs = new InMemoryFileSystem(Map.empty) + val path = Path("/a/b.log") + fs.append(path, "first") + val snapshot = fs.allFiles + val timestamp = fs.getLastChangedTimestamp(path) + fs.append(Path("/a/c/../b.log"), " second") + assert(fs.read("/a/b.log") == "first second") + assert(fs.getLastChangedTimestamp(path) == timestamp + 1) + assert(snapshot("/a/b.log") == "first", "Previously read contents must remain immutable") + fs.write(path, "replacement") + assert(fs.read(path) == "replacement") diff --git a/hkmc2/jvm/src/main/scala/hkmc2/io/PlatformFileSystem.scala b/hkmc2/jvm/src/main/scala/hkmc2/io/PlatformFileSystem.scala index c711e78395..c0f6bc548f 100644 --- a/hkmc2/jvm/src/main/scala/hkmc2/io/PlatformFileSystem.scala +++ b/hkmc2/jvm/src/main/scala/hkmc2/io/PlatformFileSystem.scala @@ -8,6 +8,8 @@ private[io] class JavaFileSystem extends FileSystem: def write(path: Path, content: String): Unit = os.write.over(unwrap(path), content) + def append(path: Path, content: String): Unit = os.write.append(unwrap(path), content) + def exists(path: Path): Bool = os.exists(unwrap(path)) def getLastChangedTimestamp(path: Path): Long = os.mtime(unwrap(path)) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index 6fabb90896..e86319ffcf 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -138,6 +138,7 @@ class CompilerCacheTest extends AnyFunSuite: underlyingFs.read(path) def write(path: io.Path, content: String): Unit = underlyingFs.write(path, content) + def append(path: io.Path, content: String): Unit = underlyingFs.append(path, content) def exists(path: io.Path): Boolean = underlyingFs.exists(path) def getLastChangedTimestamp(path: io.Path): Long = underlyingFs.getLastChangedTimestamp(path) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala index 3b2b4dcb31..0b400ae555 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala @@ -20,8 +20,7 @@ class MarksTest extends AnyFunSuite: given ResolutionContext = owner.newResolverState.context given Raise = diagnostic => fail(diagnostic.theMsg) given DebugPrinter = new DebugPrinter - given TraceLogger = new TraceLogger: - override def doTrace: Boolean = false + given TraceLogger = TraceLogger.silent // Path normalization needs a value that retains scope exits. Dynamic values // omit those exits because they carry no references into their source scope. val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/SubstitutionGrowthTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/SubstitutionGrowthTest.scala index fb40e089b5..c915671164 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/SubstitutionGrowthTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/SubstitutionGrowthTest.scala @@ -47,9 +47,8 @@ class SubstitutionGrowthTest extends AnyFunSuite: private final case class Measurement(views: Int, largestSubstitution: Int, activations: Int, typeInstances: Int) private val cctx = TestFolders.compilerCtx(os.pwd) - private given DebugPrinter = new DebugPrinter - private val tl = new TraceLogger: - override def doTrace: Bool = false + private given DebugPrinter = new DebugPrinter() + private val tl = TraceLogger.silent /** Resolves a generated unit through ordinary elaboration, in a fresh state. */ private def measure(name: Str, source: Str): Measurement = @@ -129,8 +128,7 @@ class SubstitutionGrowthTest extends AnyFunSuite: given owner: Elaborator.State = new Elaborator.State given state: ResolutionContext = owner.newResolverState.context given Raise = diagnostic => fail(diagnostic.theMsg) - given trace: TraceLogger = new TraceLogger: - override def doTrace: Boolean = false + given trace: TraceLogger = TraceLogger.silent given Config = Config.default(TestFolders.mainTestDir(os.pwd)) given CompilerCtx = CompilerCtx.fresh(io.FileSystem.default, TestFolders.compilerPaths(os.pwd), summon[Config]) val resolver = Elaborator(trace, TestFolders.mainTestDir(os.pwd), Elaborator.Ctx.empty) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 6cdc75ba3d..ead3d9cd48 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -13,9 +13,8 @@ class TypeRelationTest extends AnyFunSuite: given owner: Elaborator.State = new Elaborator.State given state: ResolutionContext = owner.newResolverState.context given Raise = diagnostic => fail(diagnostic.theMsg) - given DebugPrinter = new DebugPrinter - given trace: TraceLogger = new TraceLogger: - override def doTrace: Boolean = false + given DebugPrinter = new DebugPrinter() + given trace: TraceLogger = TraceLogger.silent given Config = Config.default(TestFolders.mainTestDir(os.pwd)) given CompilerCtx = CompilerCtx.fresh(io.FileSystem.default, TestFolders.compilerPaths(os.pwd), summon[Config]) val resolver = Elaborator(trace, TestFolders.mainTestDir(os.pwd), Elaborator.Ctx.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index d38f82e841..d76fc05a7a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -31,6 +31,13 @@ class CompilerCtx( * symbols they define — must not depend on which of them is compiled or imported first. */ val rootConfig: Config, ): + private val outputHandler: DebugOutputHandler = importing match + case S((_, parent)) => parent.outputHandler + case N => DebugOutputHandler(fs, println) + + private abstract class PhaseTraceLogger(context: DebugContext)(using DebugPrinter) + extends TraceLogger(context, outputHandler, indentWhenDisabled = true, prefix = ""): + protected final def forceTrace(scope: Opt[Str]): Bool = false def allFilesBeingImported: Ls[io.Path] = importing match @@ -69,6 +76,7 @@ class CompilerCtx( def mk = val dependencies = new CompilerCtx.DependencyRecorder val modulePath = (file.up / io.RelPath(file.baseName + ".mjs")).toString + val debug = new DebugContext val state = new Elaborator.State given Elaborator.State = state @@ -81,20 +89,27 @@ class CompilerCtx( includeZero = true, )) ) - val backendTL = new TraceLogger: - override def doTrace: Bool = false + val backendTL = TraceLogger.silent val parse = given CompilerCtx = this - ParserSetup(file) + ParserSetup(file, outputHandler) + val parsed = parse.resultBlk + val phaseConfig = Config.elaborationConfig(parsed, file.up) + debug.configure(phaseConfig.debug) + val etl = new PhaseTraceLogger(debug): + protected def phaseEnabled(config: Config.Debug): Bool = config.elaboration + val ltl = new PhaseTraceLogger(debug): + protected def phaseEnabled(config: Config.Debug): Bool = config.lowering + val dtl = new PhaseTraceLogger(debug): + protected def phaseEnabled(config: Config.Debug): Bool = config.optimizations + given Elaborator.Ctx = prelude val artifactCtx = derive(parse.origin.fileName, dependencies) - val parsed = parse.resultBlk - val elaborationConfig = Config.elaborationConfig(parsed) val elab = given CompilerCtx = artifactCtx - given Config = elaborationConfig - Elaborator(tl, file.up, prelude) + given Config = phaseConfig + Elaborator(etl, file.up, prelude) val nme = file.baseName val exportedSymbol = parsed.definedSymbols.find(_._1 === nme).map(_._2) @@ -110,12 +125,18 @@ class CompilerCtx( // The runtime itself supplies these symbols once its own body is elaborated. if !isRuntime then state.initRuntimeSymbolsFromFile(paths.runtimeSourceFile, prelude)( - using tl, summon[Raise], artifactCtx) - val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed, exportedSymbol.toList)) + using etl, summon[Raise], artifactCtx) + val (blk0, _) = phaseConfig.givenIn(elab.importFrom(parsed, exportedSymbol.toList)) if isRuntime then state.initRuntimeSymbolsFromBlock(blk0) val artifactConfig = Config.extractConfigFromStats(blk0) + def display(title: Str, contents: Str, out: Config.DebugOutput): Unit = + outputHandler.emit(out, s"${title}\n${contents}") + DebugOutputHandler.showElaboratedTree(blk0, artifactConfig)(display) + DebugOutputHandler.showElaboratedTerm(blk0, artifactConfig, 100)(display)(using + Scope.empty(Scope.Cfg.default.copy(escapeChars = false, useSuperscripts = true, includeZero = true)), + summon[Raise]) if !artifactConfig.language.useNewResolution then artifactConfig.givenIn: given Elaborator.State = state val resolver = Resolver(backendTL) @@ -151,12 +172,30 @@ class CompilerCtx( // and only fully-transformed definitions are valid to splice into another unit. def optimize(lowered: codegen.Program): codegen.Program = given Config = artifactConfig - val printer = (p: codegen.Program) => p.showAsTree // TODO: proper printing like in diff-tests + given ShowCfg = ShowCfg( + showErasedTypes = false, + showExpansionMappings = false, + showFlowSymbols = true, + debug = false, + ) + val irPrintingScp = Scope.empty(Scope.Cfg.default.copy( + escapeChars = false, + useSuperscripts = false, + includeZero = false, + )) + val blockPrinter = codegen.Printer() + val printer = (p: codegen.Program) => + blockPrinter.worksheet(p)(using irPrintingScp).mkString(100) val pipeline = new codegen.CompilationPipeline: override def extraSymbolsToPreserveFrom(prog: codegen.Program): Set[codegen.ScopedSymbol] = collectCompilationUnitSymbols(prog) - backendTL.givenIn: - pipeline.run(lowered, printer, exportedSymbol.toSet, backendTL) + override def preOptimizeHook(prog: codegen.Program): Unit = + DebugOutputHandler.showLoweredTree(prog)(display) + DebugOutputHandler.showIR(prog, false, blockPrinter, 100)(display)(using artifactConfig, irPrintingScp) + val optimized = ltl.givenIn: + pipeline.run(lowered, printer, exportedSymbol.toSet, dtl) + DebugOutputHandler.showIR(optimized, true, blockPrinter, 100)(display)(using artifactConfig, irPrintingScp) + optimized // Every compilation unit is lowered, so that its symbols carry the IR definitions an // importer's inliner may splice in — and so that the importer never has to lower it itself, @@ -165,8 +204,8 @@ class CompilerCtx( artifactConfig.givenIn: given Elaborator.State = state given codegen.Erasure = codegen.Erasure(blk)(using artifactConfig, prelude, state) - val low = backendTL.givenIn: - new codegen.Lowering()(using artifactConfig, backendTL, summon[Raise], state, prelude, summon[SymbolPrinter]) + val low = ltl.givenIn: + new codegen.Lowering()(using artifactConfig, ltl, summon[Raise], state, prelude, summon[SymbolPrinter]) optimize(low.program(blk, Set.empty)) state.publishCompilationUnitAbi: @@ -214,19 +253,24 @@ class CompilerCtx( s"Cached prelude for $file was elaborated under a different root configuration") art.lastChangedTimestamp >= lastMod, create = + val debug = new DebugContext val state = new Elaborator.State given Elaborator.State = state given Config = rootConfig given CompilerCtx = this - val parse = ParserSetup(file) - val elaborationConfig = Config.elaborationConfig(parse.resultBlk) + given DebugPrinter = new DebugPrinter(S(state)) + val parse = ParserSetup(file, outputHandler) + val elaborationConfig = Config.elaborationConfig(parse.resultBlk, file.up) + debug.configure(elaborationConfig.debug) + val etl = new PhaseTraceLogger(debug): + protected def phaseEnabled(config: Config.Debug): Bool = config.elaboration // The prelude defines the builtins consulted during its own resolution. // Seed lookup with the block's original symbols, as block elaboration does, // so those lookups share the declarations that are being elaborated. val initCtx = State.init.nestLocal("prelude").withMembers(parse.resultBlk.definedSymbols) val elab = given Config = elaborationConfig - Elaborator(tl, file.up, initCtx) + Elaborator(etl, file.up, initCtx) val (blk, ctx) = elaborationConfig.givenIn(elab.importFrom(parse.resultBlk, Nil)(using initCtx)) // Prelude declarations have no executable program, but their signatures and nominal hierarchy // must be erased before any compilation unit can use them. diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 76bf816634..f05867d244 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -33,6 +33,7 @@ case class Config( commentGeneratedCode: Bool, noFreeze: Bool, noModuleCheck: Bool, + debug: Debug, optimizer: Optimizer, // Check internal invariants whose checks are too costly to always run, such as MemberStorage.privateOwner's. checkInvariants: Bool, @@ -87,6 +88,7 @@ object Config: commentGeneratedCode = false, noFreeze = false, noModuleCheck = false, + debug = Debug.default, optimizer = Optimizer.FastOpt, checkInvariants = false, ) @@ -192,6 +194,40 @@ object Config: // TODO case class TypeChecking() + + enum DebugOutput: + case StdIO + /** A relative path written in a directive is resolved, when the directive is parsed, + * against the directory of the file in which it is written, as for imports. */ + case File(path: io.Path) + + case class Debug( + showParsedTree: Bool, + elaboration: Bool, + showElaboratedTree: Bool, + showElaboratedTerm: Bool, + lowering: Bool, + showLoweredTree: Bool, + optimizations: Bool, + showIR: Bool, + showOptimizedIR: Bool, + showUids: Bool, + out: DebugOutput, + ) + object Debug: + val default = Debug( + showParsedTree = false, + elaboration = false, + showElaboratedTree = false, + showElaboratedTerm = false, + lowering = false, + showLoweredTree = false, + optimizations = false, + showIR = false, + showOptimizedIR = false, + showUids = true, + out = DebugOutput.StdIO, + ) case class SanityChecks(light: Bool, checkUnreachable: Bool) @@ -284,15 +320,21 @@ object Config: /** File directives apply to the whole compilation unit, including elaboration. * Read them before constructing the elaborator so its resolver agrees with lowering. * Elaboration still records SetConfig statements and reports directive errors once. */ - def elaborationConfig(prgm: syntax.Tree.Block)(using Config): Config = + def elaborationConfig(prgm: syntax.Tree.Block, sourceDir: io.Path)(using Config): Config = + elaborationConfig(prgm.desugStmts, sourceDir) + + /** Read directives from parsed trees before elaboration or tree display. + * `sourceDir` is the directory of the file containing the trees. */ + def elaborationConfig(trees: Ls[syntax.Tree], sourceDir: io.Path)(using Config): Config = import syntax.Tree.* given Raise = _ => () def modifier(tree: syntax.Tree): Config => Config = tree match case App(Directive(prefix, _), args) => modifier(Directive(prefix, args)) case Directive(Ident("config"), Tup(args)) => ConfigParser.parseOverrides(args) case Directive(Ident("lang"), Tup(args)) => ConfigParser.parseLanguageDirective(args) + case Directive(Ident("dbg"), Tup(args)) => ConfigParser.parseDebugDirective(args, definitionLocal = false, sourceDir) case _ => identity - prgm.desugStmts.foldLeft(config)((cfg, tree) => modifier(tree)(cfg)) + trees.foldLeft(config)((cfg, tree) => modifier(tree)(cfg)) def extractConfigFromStats(prgm: semantics.Term.Blk)(using Config) = // Extract cumulative config modifications from SetConfig statements @@ -540,6 +582,72 @@ object ConfigParser: cfg => cfg.copy(language = modify(cfg.language)) case N => identity + private def parseDebugOutput(tree: Tree, sourceDir: io.Path)(using Raise): Opt[Config.DebugOutput] = tree match + case Ident("StdIO") | Call("StdIO", Nil) => + S(Config.DebugOutput.StdIO) + case Call("File", StrLit(path) :: Nil) => + S(Config.DebugOutput.File(io.Path.resolve(sourceDir, path))) + case _ => + expect("StdIO or File(\"path\")")(tree) + N + + private def updateDebugFlag(name: Str, value: Bool, tree: Tree, definitionLocal: Bool)(using Raise): Config.Debug => Config.Debug = + if definitionLocal && name === "optimizations" then + raise(WarningReport( + msg"Debugging aspect '${name}' is not supported in @dbg; use #dbg(${name}: ${value.toString}) instead." -> tree.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + name match + case "showParsedTree" => _.copy(showParsedTree = value) + case "elaboration" => _.copy(elaboration = value) + case "showElaboratedTree" => _.copy(showElaboratedTree = value) + case "showElaboratedTerm" => _.copy(showElaboratedTerm = value) + case "lowering" => _.copy(lowering = value) + case "showLoweredTree" => _.copy(showLoweredTree = value) + case "optimizations" => _.copy(optimizations = value) + case "showIR" => _.copy(showIR = value) + case "showOptimizedIR" => _.copy(showOptimizedIR = value) + case "showUids" => _.copy(showUids = value) + case _ => + raise(ErrorReport( + msg"Unknown debugging aspect '${name}'" -> tree.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + identity + + private def parseDebugArg(arg: Tree, definitionLocal: Bool, sourceDir: io.Path)(using Raise): Config.Debug => Config.Debug = arg match + case Quoted(Ident(name)) => + updateDebugFlag(name, true, arg, definitionLocal) + case Ident(name) if name.startsWith("'") => + updateDebugFlag(name.drop(1), true, arg, definitionLocal) + case NamedArg("out", value) => + parseDebugOutput(value, sourceDir) match + case S(out) => _.copy(out = out) + case N => identity + case NamedArg(name, value) => + parseBool(value) match + case S(enabled) => updateDebugFlag(name, enabled, arg, definitionLocal) + case N => identity + case _ => + unsupported("debugging", arg) + identity + + /** Parse `#dbg(...)` and `@dbg(...)` arguments into a cumulative debug configuration. + * `sourceDir` is the directory of the file containing them. */ + def parseDebugDirective(args: Ls[Tree], definitionLocal: Bool, sourceDir: io.Path)(using Raise): Config => Config = + val modifyDebug = args.foldLeft(identity[Config.Debug]): (acc, arg) => + val modify = parseDebugArg(arg, definitionLocal, sourceDir) + debug => modify(acc(debug)) + new (Config => Config): + def apply(cfg: Config): Config = cfg.copy(debug = modifyDebug(cfg.debug)) + override def toString: Str = "..." + + /** Select parsed-tree displays without duplicating diagnostics reported by elaboration. */ + def discoverDebugFromAnnotations(annotations: Ls[Tree], base: Config, sourceDir: io.Path): Opt[Config] = + given Raise = _ => () + val modifiers = annotations.collect: + case App(Ident("dbg"), Tup(args)) => parseDebugDirective(args, definitionLocal = true, sourceDir) + if modifiers.isEmpty then N + else S(modifiers.foldLeft(base)((cfg, modify) => modify(cfg))) + /** Parse the `None`/`Some(...)` syntax for optional config fields. * Also accepts unwrapped values as a convenience (treated as `Some(value)`). */ private def parseOpt[A](tree: Tree)(parseInner: Tree => Opt[A])(using Raise): Opt[Opt[A]] = tree match diff --git a/hkmc2/shared/src/main/scala/hkmc2/DebugContext.scala b/hkmc2/shared/src/main/scala/hkmc2/DebugContext.scala new file mode 100644 index 0000000000..a366584031 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/DebugContext.scala @@ -0,0 +1,35 @@ +package hkmc2 + +import hkmc2.utils.*, shorthands.* + + +/** Source debug settings shared by a compilation unit's phase loggers. + * Imported units have their own context; only their output handler is shared. + * Compilation is synchronous within a unit, so scoped changes are restored on the call stack. */ +class DebugContext: + protected def forceUids: Bool = false + + private case class Settings(debug: Config.Debug, uidDebug: Bool) + private var current = Settings(Config.Debug.default, false) + + final def config: Config.Debug = current.debug + final def showUids: Bool = (current.uidDebug || forceUids) && config.showUids + + /** File directives are discovered after parsing; diff-test blocks also replace their base settings. */ + final def configure(debug: Config.Debug): Unit = + current = Settings(debug, debug.elaboration || debug.lowering || debug.optimizations) + + private def within[T](settings: Settings)(body: => T): T = + val saved = current + current = settings + try body finally current = saved + + /** A lowering continuation processes following statements in the enclosing settings. + * Capture those settings at entry, independently of scopes entered while processing the definition. */ + final def scopedThen[R, T](config: Config.Debug, uidDebug: Bool) + (process: (R => T) => T)(k: R => T): T = + val outer = current + within(Settings(config, uidDebug)): + process(r => within(outer)(k(r))) + +end DebugContext diff --git a/hkmc2/shared/src/main/scala/hkmc2/DebugOutputHandler.scala b/hkmc2/shared/src/main/scala/hkmc2/DebugOutputHandler.scala new file mode 100644 index 0000000000..6853cc97a7 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/DebugOutputHandler.scala @@ -0,0 +1,105 @@ +package hkmc2 + +import scala.collection.mutable + +import hkmc2.utils.*, shorthands.* + + +/** A compilation session shares one handler across units: each output file is overwritten on first + * use, then appended to. Concurrent debug writes are not synchronized. */ +final class DebugOutputHandler(fs: io.FileSystem, stdIO: Str => Unit) extends DebugSink: + private val writtenFiles = mutable.Set.empty[io.Path] + + def emit(out: Config.DebugOutput, line: Str): Unit = out match + case Config.DebugOutput.StdIO => stdIO(line) + case Config.DebugOutput.File(file) => + if writtenFiles(file) then fs.append(file, line + '\n') + else + fs.write(file, line + '\n') + writtenFiles += file + +object DebugOutputHandler: + + def definitionConfig(annotations: Ls[semantics.Annot], base: Config): Opt[Config] = + val modifiers = annotations.collect: + case semantics.Annot.Debug(modify) => modify + if modifiers.isEmpty then N + else S(modifiers.foldLeft(base)((cfg, modify) => modify(cfg))) + + def showParsedDefinitions(trees: Ls[syntax.Tree], config: Config, sourceDir: io.Path) + (display: (syntax.Tree, Config.DebugOutput) => Unit): Unit = + def visit(tree: syntax.Tree): Unit = tree match + case syntax.PossiblyAnnotated(annotations, target) => + ConfigParser.discoverDebugFromAnnotations(annotations, config, sourceDir).foreach: localConfig => + if localConfig.debug.showParsedTree then display(tree, localConfig.debug.out) + target.children.foreach: + case child: syntax.Tree => visit(child) + case _ => () + case _ => () + trees.foreach(visit) + + def showElaboratedTree(block: semantics.Term.Blk, config: Config) + (display: (Str, Str, Config.DebugOutput) => Unit)(using DebugPrinter): Unit = + showElaborated(block, config, _.showElaboratedTree, "Elaborated tree", _.showAsTree, _.showAsTree)(display) + + def showElaboratedTerm(block: semantics.Term.Blk, config: Config, width: Int) + (display: (Str, Str, Config.DebugOutput) => Unit)(using utils.Scope, Raise): Unit = + given semantics.ShowCfg = semantics.ShowCfg( + showErasedTypes = false, + showExpansionMappings = true, + showFlowSymbols = true, // Also disambiguates symbols that share a name. + debug = false, + ) + showElaborated(block, config, _.showElaboratedTerm, "Elaborated term", + _.showTopLevel.mkString(width), _.show.mkString(width))(display) + + /** Display the whole block if the file's settings select it, and otherwise each annotated + * definition that selects it, including definitions nested in other definitions. */ + private def showElaborated(block: semantics.Term.Blk, config: Config, isEnabled: Config.Debug => Bool, title: Str, + showBlock: semantics.Term.Blk => Str, showDefinition: semantics.Definition => Str) + (display: (Str, Str, Config.DebugOutput) => Unit): Unit = + def visit(statement: semantics.Statement): Unit = + statement match + case defn: semantics.Definition => + definitionConfig(defn.annotations, config).foreach: localConfig => + if isEnabled(localConfig.debug) then display(title, showDefinition(defn), localConfig.debug.out) + case _ => () + statement.subStatements.foreach(visit) + if isEnabled(config.debug) then display(title, showBlock(block), config.debug.out) + else visit(block) + + def showIR(program: codegen.Program, optimized: Bool, printer: codegen.Printer, width: Int) + (display: (Str, Str, Config.DebugOutput) => Unit)(using Config, utils.Scope): Unit = + val title = if optimized then "Optimized IR" else "Lowered IR" + val isEnabled: Config.Debug => Bool = if optimized then _.showOptimizedIR else _.showIR + if isEnabled(config.debug) then + display(title, printer.worksheet(program).mkString(width), config.debug.out) + else forEachIRDefinition(program, isEnabled): (defn, out) => + display(title, printer.printDefinition(defn).mkString(width), out) + + def showLoweredTree(program: codegen.Program)(display: (Str, Str, Config.DebugOutput) => Unit) + (using Config, DebugPrinter): Unit = + if config.debug.showLoweredTree then + display("Lowered IR Tree", program.showAsTree, config.debug.out) + else forEachIRDefinition(program, _.showLoweredTree): (defn, out) => + val tree = defn match + case product: Product => product.showAsTree + display("Lowered IR Tree", tree, out) + + /** Methods are visited directly by BlockTraverser, without passing through applyDefn. */ + private def forEachIRDefinition(program: codegen.Program, isEnabled: Config.Debug => Bool) + (display: (codegen.Defn, Config.DebugOutput) => Unit): Unit = + def visit(defn: codegen.Defn): Unit = + defn.configOverride.foreach: localConfig => + if isEnabled(localConfig.debug) then display(defn, localConfig.debug.out) + new codegen.BlockTraverser: + override def applyFunDefn(fun: codegen.FunDefn): Unit = + visit(fun) + super.applyFunDefn(fun) + override def applyValDefn(defn: codegen.ValDefn): Unit = + visit(defn) + super.applyValDefn(defn) + override def applyClsLikeDefn(defn: codegen.ClsLikeDefn): Unit = + visit(defn) + super.applyClsLikeDefn(defn) + .applyProgram(program) diff --git a/hkmc2/shared/src/main/scala/hkmc2/DebugSink.scala b/hkmc2/shared/src/main/scala/hkmc2/DebugSink.scala new file mode 100644 index 0000000000..c46dd40367 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/DebugSink.scala @@ -0,0 +1,12 @@ +package hkmc2 + +import hkmc2.utils.*, shorthands.* + + +/** Sharing a sink shares output delivery; each logger decides which messages to emit and how to format them. */ +trait DebugSink: + def emit(out: Config.DebugOutput, line: Str): Unit + +object DebugSink: + object discard extends DebugSink: + def emit(out: Config.DebugOutput, line: Str): Unit = () diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 947f6520f1..ccd9929e8f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -7,7 +7,8 @@ import utils.* import hkmc2.semantics.* -class ParserSetup(file: io.Path)(using Elaborator.State, Raise, CompilerCtx): +class ParserSetup(file: io.Path, outputHandler: DebugOutputHandler) + (using Elaborator.State, Raise, CompilerCtx, Config, DebugPrinter): val block = CompilerCtx.get.fs.read(file) val fph = new FastParseHelpers(block) @@ -18,9 +19,21 @@ class ParserSetup(file: io.Path)(using Elaborator.State, Raise, CompilerCtx): val rules = syntax.ParseRules() val parser = new syntax.Parser(origin, tokens, rules, raise, dbg = false): + // * Tracing is not supported for compiled files, + // * as debug flags to enable traces are only available after parsing. def doPrintDbg(msg: => Str): Unit = () val result = parser.parseAll(parser.block(allowNewlines = true)) + val effectiveConfig = Config.elaborationConfig(result, file.up) + + if effectiveConfig.debug.showParsedTree + then + outputHandler.emit( + effectiveConfig.debug.out, + s"Parsed tree\n${result.map(_.showAsTree).mkString("\n")}", + ) + else DebugOutputHandler.showParsedDefinitions(result, effectiveConfig, file.up): (tree, out) => + outputHandler.emit(out, s"Parsed tree\n${tree.showAsTree}") val resultBlk = new syntax.Tree.Block(result) @@ -50,10 +63,9 @@ class MLsCompiler def compileModule(file: io.Path): Unit = given Raise = mkRaise(file) - given DebugPrinter = new DebugPrinter + given DebugPrinter = new DebugPrinter(N) - val compilerTL = new TraceLogger: - override def doTrace: Bool = false + val compilerTL = TraceLogger.silent val preludeCtx = cctx.getPrelude(preludeFile)(using compilerTL, summon[Raise]).ctx val artifact = cctx.getElaboratedBlock(file, preludeCtx)(using compilerTL) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 2b98f54420..25389d7c2c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -60,10 +60,10 @@ class BlockSimplifier iteration += 1 if iteration > MaxIterations then - log(s"⬤ Reached maximum number of iterations ($MaxIterations), stopping simplifications") + log(dbg"⬤ Reached maximum number of iterations ($MaxIterations), stopping simplifications") return res - log(s"⬤ Simplif. iter. $iteration") + log(dbg"⬤ Simplif. iter. $iteration") if summon[Config].optimizer.deadCodeElim then // * Running DCE once sometimes produces more DCE opportunities; @@ -113,7 +113,7 @@ class BlockSimplifier var changed = false def registerChange(dbg: => Str)(using Line) = - log(s"!! Change triggered { ${dbg} }") + log(dbg"!! Change triggered { ${dbg} }") // * For debugging: // log(s"!! Change triggered { ${dbg} } at ${summon[FileName].value}:${summon[Line].value}") changed = true @@ -345,7 +345,7 @@ class BlockSimplifier // * Remove useless break case Break(label) if tailLabels.contains(label) => - log(s"Break ${label} is eliminated: current tail label list is ${tailLabels}") + log(dbg"Break ${label} is eliminated: current tail label list is ${tailLabels}") registerChange(s"rm tail-position break ${label.showDbg}") End() @@ -487,7 +487,7 @@ class BlockSimplifier end new - log(s"Captured variables: ${capturedVars}") + log(dbg"Captured variables: ${capturedVars}") cur = applyProgram(prog) @@ -805,7 +805,7 @@ class BlockSimplifier private def showMap: Str = assignedResults .iterator.map: (k, v) => - s"${k.showDbg} -> ${v.toString}" + s"${k.showDbg} -> ${v.showAsPlain}" .mkString("{", ", ", "}") private def canCollapseImmediateCallPrefix( @@ -962,7 +962,7 @@ class BlockSimplifier // * might end up being live even though local flow analysis would think they are not. sub.definedVars.foreach: case sym: LocalVar => - log(s"Variable ${sym.showDbg} is written in a `finally` block; marking it as imprecise tracked") + log(dbg"Variable ${sym.showDbg} is written in a `finally` block; marking it as imprecise tracked") impreciselyTrackedVars += sym case _ => applyBlock(finallyDo) @@ -1031,7 +1031,7 @@ class BlockSimplifier var shapes = if deadBranchRemoval then getShapes(scrut2) else giveUp // TODO: if analysis gave up, make the shapes the set of cases of the patmat, to rm redundant arms - if !gaveUp then log(s"Initial shapes: ${shapes}") + if !gaveUp then log(dbg"Initial shapes: ${shapes}") val oldAssigned = assignedResults var branchAssigneds = List.empty[AssignedResults] @@ -1078,7 +1078,7 @@ class BlockSimplifier shapes.contains(sym) && { shapes -= sym; true } || regChange("Impossible instanceof") case _ => true - if !gaveUp then log(s"Filtered arms: ${arms2.map(_._1)}") + if !gaveUp then log(dbg"Filtered arms: ${arms2.map(_._1)}") val newArms = arms2.mapConserve: case arm @ (cse, body) => @@ -1865,7 +1865,7 @@ class BlockSimplifier override def applyBlock(blk: Block) = blk match case Define(defn: FunDefn, rest) if m.get(defn.dSym).exists(_.canBeInlineEliminated) => - log(s"Inline elimination: ${defn.dSym}") + log(dbg"Inline elimination: ${defn.dSym}") registerChange(s"rm inline-eliminated function ${defn.dSym.showDbg}") applyBlock(rest) case _ => super.applyBlock(blk) @@ -1914,7 +1914,7 @@ class BlockSimplifier super.applyResult(call)(k) case S(matchedArgs) => registerChange(s"inline call ${callee.showDbg}") - log(s"Inline call for ${callee}, with args ${argss}") + log(dbg"Inline call for ${callee}, with args ${argss}") val extraArgss = argss.drop(info.defn.params.length) def go(acc: Block => Block, args: List[(VarSymbol, Result)], mapping: Map[Symbol, Symbol]): Block = args match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala index 74f1b7a53f..34fe1b3a72 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala @@ -46,11 +46,11 @@ class CompilationPipeline(using Config, Raise, State, Ctx, SymbolPrinter): blockPass(Lifter(_).transform)(prog) else prog runPass("Deforest"): prog => - val outterTl = tl + val outerTl = tl config.deforest match case None => prog case Some(dCfg) => - flowAnalysis.FlowAnalysis.mkTraceLogger(dCfg.config, "deforest > ", outterTl).givenIn: + flowAnalysis.FlowAnalysis.mkTraceLogger(dCfg.config, "deforest > ", outerTl.sink)(using outerTl.debugPrinter).givenIn: deforest.Deforest(prog, additionalTopLevelSymbols = symbolsToPreserve.collect{case s: semantics.LocalVarSymbol => s}) runPass("HandlerLowering"): prog => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadConstructorElim.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadConstructorElim.scala index 2c2634d564..6982c55934 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadConstructorElim.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadConstructorElim.scala @@ -74,10 +74,10 @@ class DeadConstructorElimSolver(val constraintSolver: FlowConstraintSolver, trac .filterNot(liveCtorConcreteIds) end deadCtors - if tl.doTrace then + if tl.isTracing then tl.log(">>> dead-constructor-elim results >>>") for ctorStr <- deadCtors.toSeq.map(_.exprId.getResult.showDbg).sorted do - tl.log(s"eliminable: $ctorStr") + tl.log(dbg"eliminable: $ctorStr") tl.log("<<< dead-constructor-elim results <<<") end if end DeadConstructorElimSolver diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala index fb241111dd..3c9e3d2b08 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala @@ -115,13 +115,13 @@ class DeadParamElimSolver(val constraintSolver: FlowConstraintSolver, traceLogge case None => eliminableCallSiteArgsById(consFun.concreteId) = eliminable.toSet case S(existing) => assert(existing.toList.sorted === eliminable) - if tl.doTrace then + if tl.isTracing then given ShowCfg = ShowCfg.internal assert(eliminableCallSiteArgsById.nonEmpty === eliminableParamsById.nonEmpty) tl.log(">>> dead-param-elim results >>>") for (id, elim) <- eliminableParamsById do - tl.log(s"${id.showConcreteFunId} -> eliminable: {${elim.toSeq.sorted.mkString(", ")}}") + tl.log(dbg"${id.showConcreteFunId} -> eliminable: {${elim.toSeq.sorted.mkString(", ")}}") tl.log("<<< dead-param-elim results <<<") end if end DeadParamElimSolver diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala index cad92575d6..9ca175614c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala @@ -140,12 +140,12 @@ class EtaExpansionSolver(val constraintSolver: FlowConstraintSolver, tl: TraceLo case (_: TermSymbol, 0) => addFunShapeIfChanged() case _ => () - if tl.doTrace then + if tl.isTracing then given ShowCfg = ShowCfg.internal tl.log(">>> eta-expansion targets shapes >>>") for (id, shape) <- etaExpandedFunShape do - tl.log(s"${id.showConcreteFunId}: ${shape.map(_.ppParamInfo).mkString("[", ", ", "]")}") + tl.log(dbg"${id.showConcreteFunId}: ${shape.map(_.ppParamInfo).mkString("[", ", ", "]")}") tl.log("<<< eta-expansion targets shapes <<<") end if end EtaExpansionSolver diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala index 8f92f74930..2c66104cd4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala @@ -293,7 +293,7 @@ object FlowAnalysisBasedRewrite: symbolPrinter: SymbolPrinter, ): Program = def mkTl(prefix: Str, debug: Bool) = - FlowAnalysis.mkTraceLogger(optCfg.config.copy(debug = debug), prefix, tl) + FlowAnalysis.mkTraceLogger(optCfg.config.copy(debug = debug), prefix, tl.sink)(using tl.debugPrinter) val flowAnalysisRes = mkTl("flow-analysis > ", optCfg.debug).givenIn: FlowAnalysis( p, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index e3e3cb5044..8162618db5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -588,7 +588,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R // without many special handling. val newCtor = if opt.fold(true)(_.doNotInstrumentTopLevelModCtor) && !h.currentBlockIsTrulyNested then bod.ctor else translateCtorLike(bod.ctor, bod.isym.asThis, true) - tl.log(s"companion name: ${bod.isym.nme}") + tl.log(dbg"companion name: ${bod.isym.nme}") if (bod.methods is newMtds) && (bod.ctor is newCtor) then bod else diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 687d850ffb..0cd446008c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -94,7 +94,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu private def erroneousRef(ref: NewResolvable): Bool = ref.isErroneous || ref.resolvedTargets.exists(_.defn.exists(_.isErroneous)) - /** Lower the parent before the class body and share its value with every super reference. + /** Lower the parent reference before the class body. * Definition references are stable; classOf already saves computed class values in temporaries. * Other paths (notably a class selected through a mutable receiver) must be read here, once, * so changing that receiver after the class definition cannot redirect its super calls. @@ -104,20 +104,33 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val saved = loweringCtx.registerTempSymbol(N, erasedType = N, "superBase") Assign(saved, path, k(saved.asSimpleRef)) parent match - case Value.MemberRef(_, _: ClassSymbol) | Value.SimpleRef(_: TempSymbol) => k(parent) - case sel: Select if sel.symbol.exists(_.isInstanceOf[ClassSymbol]) => - // The selected class declaration is immutable, but its receiver might be a variable. - // Save that receiver while retaining the class symbol for nominal IR analyses. - sel.qual match - case _: Value.This | Value.MemberRef(_, _: ClassSymbol | _: ModuleOrObjectSymbol) | - Value.SimpleRef(_: TempSymbol) => k(sel) - case qual => save(qual)(p => k(sel.copy(qual = p)(sel.symbol, sel.toLoc)(sel.sanitize))) - case _ => save(parent)(k) + case Value.MemberRef(_, _: ClassSymbol) | Value.SimpleRef(_: TempSymbol) => k(parent) + case sel: Select if sel.symbol.exists(_.isInstanceOf[ClassSymbol]) => + // The selected class declaration is immutable, but its receiver might be a variable. + // Save that receiver while retaining the class symbol for nominal IR analyses. + sel.qual match + case _: Value.This | Value.MemberRef(_, _: ClassSymbol | _: ModuleOrObjectSymbol) + | Value.SimpleRef(_: TempSymbol) => k(sel) + case qual => save(qual)(p => k(sel.copy(qual = p)(sel.symbol, sel.toLoc)(sel.sanitize))) + case _ => save(parent)(k) + + private def definitionConfigOverride(annotations: Ls[Annot]): Opt[Config] = + val modifiers = annotations.collect: + case Annot.Config(modify) => modify + case Annot.Debug(modify) => modify + if modifiers.isEmpty then N + else S(modifiers.foldLeft(config)((cfg, modify) => modify(cfg))) + + private def debugLowering[T](annotations: Ls[Annot])(thunk: => T): T = + tl.traceDefinition(annotations, config)(thunk) + + private def debugLoweringThen[R](annotations: Ls[Annot])(lower: (R => Block) => Block)(k: R => Block): Block = + tl.traceDefinitionThen(annotations, config)(lower)(k) extension (t: Term) def instantiated = t match case r: Resolvable => - tl.trace[Term](s"Expanding term ${r}", post = t => s"~> ${t}"): + tl.trace[Term](dbg"Expanding term ${r}", post = t => dbg"~> ${t}"): r.expanded case t => t @@ -315,26 +328,24 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case S(bod) => td.k match case knd: syntax.Val => + assert(td.params.isEmpty) + val cfgOverride = definitionConfigOverride(td.extraAnnotations) localVal match case S(sym) => - term_nonTail(bod): r => + debugLoweringThen[Result](td.extraAnnotations)(term_nonTail(bod)(_)): r => defineSymbol(sym, r, blockImpl(stats, res)) case N => - assert(td.params.isEmpty) - val cfgOverride = td.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) - subTerm_nonTail(bod)(r => + debugLoweringThen[Path](td.extraAnnotations)(k => subTerm_nonTail(bod)(k)(using LoweringCtx.nestFunc(N))): r => // Assign(td.sym, r, // term(st.Blk(stats, res))(k))) Define( ValDefn(td.tsym, td.sym, castTo(r, td.tsym.erasedType, bod.toLoc))(cfgOverride, td.annotations), blockImpl(stats, res), - ), - )(using LoweringCtx.nestFunc(N)) + ) case syntax.Fun => - val (paramLists, bodyBlock) = setupFunctionOrByNameDef(td.params, bod, S(td.sym.nme), td.tsym.declaredResultType) - val cfgOverride = td.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) + val cfgOverride = definitionConfigOverride(td.extraAnnotations) + val (paramLists, bodyBlock) = debugLowering(td.extraAnnotations): + setupFunctionOrByNameDef(td.params, bod, S(td.sym.nme), td.tsym.declaredResultType) Define(FunDefn(td.owner, td.sym, td.tsym, paramLists, bodyBlock)(cfgOverride, td.annotations), blockImpl(stats, res)) case syntax.LetBind | syntax.HandlerBind | syntax.RecordField => fail: @@ -401,10 +412,12 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu msg"Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead." -> defn.toLoc :: Nil, source = Diagnostic.Source.Compilation )) - def defineClass(parent: Opt[Path], parentParams: Ls[ParamList]): Block = + def lowerClass(parent: Opt[Path], parentParams: Ls[ParamList]): ClsLikeDefn = val base = parent.getOrElse( Select(State.globalThisSymbol.asThis, Tree.Ident("Object"))(S(ctx.builtins.Object), defn.toLoc)(false)) - val clsDef = LoweringCtx.withSuperBase(defn.sym, base).givenIn: + // Class and module companions share one lowered definition, so class tracing may + // include its companion. Explicit module debug settings still override that scope. + debugLowering(defn.extraAnnotations)(LoweringCtx.withSuperBase(defn.sym, base).givenIn { val (mtds, publicFlds, privateFlds, ctor) = defn match case pd: PatternDef => // Compile the pattern definition into `unapply` and `unapplyStringPrefix` @@ -434,23 +447,24 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu ) case N => val (mtds, publicFlds, privateFlds, ctor) = - gatherMembers(mod.body) + debugLowering(mod.extraAnnotations)(gatherMembers(mod.body)) S(ClsLikeBody(mod.sym, mtds, privateFlds, publicFlds, ctor, mod.annotations)) case _ => N case _ => N val pctor = defn.ext.fold[Block](End()): ext => inScopedBlock(parentConstructor(parentParams, ext.args, ext.toLoc)) - val cfgOverride = defn.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) + val cfgOverride = definitionConfigOverride(defn.extraAnnotations) ClsLikeDefn(defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, parent, mtds, privateFlds, publicFlds, pctor, ctor, mod, bufferable, defn.bsym.asTrm)(cfgOverride, defn.annotations) - Define(clsDef, blockImpl(stats, res)) + }) defn.ext match - case N => defineClass(N, Nil) + case N => Define(lowerClass(N, Nil), blockImpl(stats, res)) case S(ext) => assert(k isnt syntax.Mod) // modules can't extend things and can't have super calls - classOf(ext.cls, ext): (clsp, params) => - withClassParent(clsp)(parent => defineClass(S(parent), params)) + debugLoweringThen[ClsLikeDefn](defn.extraAnnotations)(define => + classOf(ext.cls, ext): (clsp, params) => + withClassParent(clsp)(parent => define(lowerClass(S(parent), params))) + )(Define(_, blockImpl(stats, res))) case td: TypeDef => // * Type definitions are erased blockImpl(stats, res) @@ -864,7 +878,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu FlowSymbol(sym.nme) ).resolve val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme), N) - tl.log(s"Ref builtin $sym") + tl.log(dbg"Ref builtin $sym") assert(paramLists.length === 1) return k(Lambda(paramLists.head, bodyBlock)(Nil, ref.toLoc)) if sym.unary then @@ -879,7 +893,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu FlowSymbol(sym.nme) ).resolve val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme), N) - tl.log(s"Ref builtin $sym") + tl.log(dbg"Ref builtin $sym") assert(paramLists.length === 1) return k(Lambda(paramLists.head, bodyBlock)(Nil, ref.toLoc)) case bs: BlockMemberSymbol => @@ -934,9 +948,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu @tailrec final def term(t: st, inStmtPos: Bool = false)(k0: Result => Block)(using LoweringCtx): Block = - tl.log(s"Lowering.term ${t.showDbg.truncate(100, "[...]")}${ + tl.log(dbg"Lowering.term ${t.showDbg.truncate(100, "[...]")}${ if inStmtPos then " (in stmt)" else ""}${ - t.resolvedSym.fold("")(" – symbol " + _)}") + t.resolvedSym.fold("")(sym => " – symbol " + sym.showDbg)}") def warnStmt = if inStmtPos then warnPureExprInStmtPos(t.toLoc, S(t)) @@ -1677,10 +1691,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu .filterNot(_.isErroneous) // * See `erroneousRef`. .flatMap: td => td.body.map: bod => - val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme), td.tsym.declaredResultType) + val (paramLists, bodyBlock) = debugLowering(td.extraAnnotations): + setupFunctionDef(td.params, bod, S(td.sym.nme), td.tsym.declaredResultType) reportAnnotations(td, td.extraAnnotations) - val cfgOverride = td.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) + val cfgOverride = definitionConfigOverride(td.extraAnnotations) FunDefn(td.owner, td.sym, td.tsym, paramLists, bodyBlock)(cfgOverride, td.annotations) val publicFlds = clsBody.publicFlds.collect: case f if !f.tsym.isPrivate => @@ -1882,6 +1896,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case TermDefinition(k = syntax.Fun) => () case _ => warn(a) case _: Annot.Config => () // Config annotations are handled during FunDefn creation + case a: Annot.Debug => target match + case _: Definition => () // Debug annotations are handled during definition elaboration and lowering + case _ => warn(a) case annot => warn(annot) def reportAnnotations(receiver: Term, annotations: Ls[Annot]): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index f6301c0f9b..2a7b3d3e6a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -95,7 +95,7 @@ class Printer(using Config, Ctx, Raise, ShowCfg, State, SymbolPrinter): case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => doc"set ${print(lhs)}${if arrayIdx then "." else "!"}${print(fld)} = ${print(rhs)}; # ${print(rest)}" case Define(defn, rest) => - doc"${printFlags(defn)}define ${print(defn.sym)} as ${print(defn)}; # ${print(rest)}" + doc"${printDefinition(defn)}; # ${print(rest)}" case Scoped(syms, body) => scope.nest.givenIn: import hkmc2.given_Ordering_Uid // Not sure why needed... @@ -118,6 +118,9 @@ class Printer(using Config, Ctx, Raise, ShowCfg, State, SymbolPrinter): // if cfg.staged then doc"staged " else doc"" printAnnotations(defn.annotations, doc" # ") + def printDefinition(defn: Defn)(using Scope): Document = + doc"${printFlags(defn)}define ${print(defn.sym)} as ${print(defn)}" + def printAnnotations(annotations: Ls[Annot], trailing: Document)(using Scope): Document = if annotations.isEmpty then doc"" else annotations.map(_.show).mkDocument(doc" ") :: trailing diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala index d753a369e5..256a14715d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala @@ -88,7 +88,7 @@ class WorkerWrapper case Annot.NoInline() | Annot.Generator() => false case _ => true )) - log(s"▶ Worker-wrapper: ${fun.dSym.showDbg} -> ${worker.dSym.showDbg}") + log(dbg"▶ Worker-wrapper: ${fun.dSym.showDbg} -> ${worker.dSym.showDbg}") Scoped(Set.single(worker.sym), Define(wrapper, Define(worker, rest))) private def mkInlineOnly(fun: FunDefn, body: Block): FunDefn = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala index 502c013be7..86077c27db 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -125,13 +125,13 @@ class DeforestFusionSolver(val constraintSolver: FlowConstraintSolver)(using Con tl.log(">>> fusing >>>") for case (c, dest) <- finalCtorDests do - tl.log(s"${pp(c)} ->") + tl.log(dbg"${pp(c)} ->") dest match case FinalDestMatch(dtor, sels) => - tl.log(s"\tmatch: ${pp(dtor)}") - for s <- sels.toSeq.sortBy(_.exprId.uid) do tl.log(s"\tfields: ${pp(s)}") + tl.log(dbg"\tmatch: ${pp(dtor)}") + for s <- sels.toSeq.sortBy(_.exprId.uid) do tl.log(dbg"\tfields: ${pp(s)}") case FinalDestSel(dtors, field) => - tl.log(s"\tselect: ${pp(field)}") + tl.log(dbg"\tselect: ${pp(field)}") tl.log("<<< fusing <<<") end DeforestFusionSolver diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala index 220404f5a8..5123393983 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala @@ -87,16 +87,16 @@ object FlowAnalysis: def mkTraceLogger( cfg: Config.FlowAnalysisConfig, prefix: Str, - outerTl: TL, - ): TraceLogger = - new TraceLogger(using outerTl.debugPrinter): - override def doTrace: Bool = scope match + sink: DebugSink, + )(using printer: DebugPrinter): TraceLogger = + val debug = new DebugContext: + override protected def forceUids: Bool = printer.showUids + new TraceLogger(debug, sink, indentWhenDisabled = true, prefix = prefix): + protected def phaseEnabled(config: Config.Debug): Bool = false + protected def forceTrace(scope: Opt[Str]): Bool = scope match case S(TraceScope.NonAffineSyms) => cfg.logNonAffine case S(TraceScope.AccumulatorSym) => cfg.logAccumulator case _ => cfg.debug - - override def emitDbg(str: Str): Unit = - outerTl.emitDbg(s"$prefix$str") end FlowAnalysis diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index def68a1bad..8ef8616207 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -944,7 +944,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: * but the result has the same semantics. * */ def reserveNames(p: Program)(using Scope, Raise): Unit = - def go(blk: Block): Unit = tl.trace(s"avoidNames ${blk.toString.take(100)}..."): + def go(blk: Block): Unit = tl.trace(dbg"avoidNames ${blk.showAsPlain.take(100)}..."): blk match case Define(defn, rest) => defn match diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala index 16a4c0452b..8275e9d9f8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala @@ -41,7 +41,7 @@ class ConstraintSolver(infVarState: InfVarUid.State, elState: Elaborator.State, InfVar(lvl, infVarState.nextUid, new VarState(), false)(InstSymbol(sym)(using elState), hint) def extrude(ty: Type)(using lvl: Int, pol: Bool, cache: ExtrudeCache, invalctx: InvalCtx, cctx: CCtx, tl: TL): Type = - trace[Type](s"Extruding[${printPol(pol)}] ${ty.showDbg}", r => s"~> ${r.showDbg}"): + trace[Type](dbg"Extruding[${printPol(pol)}] ${ty.showDbg}", r => s"~> ${r.showDbg}"): if ty.lvl <= lvl then ty else ty.toBasic/*TODO improve extrude directly*/ match case ClassLikeType(sym, targs) => ClassLikeType(sym, targs.map { @@ -78,7 +78,7 @@ class ConstraintSolver(infVarState: InfVarUid.State, elState: Elaborator.State, case NegType(ty) => Type.mkNegType(extrude(ty)(using lvl, !pol)) case Top | Bot => ty - private def constrainConj(conj: Conj)(using InvalCtx, CCtx, TL): Unit = trace(s"Constraining ${conj.showDbg}"): + private def constrainConj(conj: Conj)(using InvalCtx, CCtx, TL): Unit = trace(dbg"Constraining ${conj.showDbg}"): conj match case Conj(i, u, (v, pol) :: tail) => var rest = Conj(i, u, tail) @@ -87,13 +87,13 @@ class ConstraintSolver(infVarState: InfVarUid.State, elState: Elaborator.State, val bd = if v.lvl >= rest.lvl then rest else extrude(rest)(using v.lvl, true, mutable.HashMap.empty) if pol then val nc = Type.mkNegType(bd).toDnf // always cache the normal form to avoid unexpected cache misses - log(s"New bound: ${v.showDbg} <: ${nc.showDbg}") + log(dbg"New bound: ${v.showDbg} <: ${nc.showDbg}") cctx.nest(v.toDnf -> nc) givenIn: v.state.upperBounds ::= nc v.state.lowerBounds.foreach(lb => constrainImpl(lb, nc)) else val c = bd.toDnf // always cache the normal form to avoid unexpected cache misses - log(s"New bound: ${v.showDbg} :> ${c.showDbg}") + log(dbg"New bound: ${v.showDbg} :> ${c.showDbg}") cctx.nest(c -> v.toDnf) givenIn: v.state.lowerBounds ::= c v.state.upperBounds.foreach(ub => constrainImpl(c, ub)) @@ -138,8 +138,8 @@ class ConstraintSolver(infVarState: InfVarUid.State, elState: Elaborator.State, private def constrainImpl(lhs: Type, rhs: Type)(using InvalCtx, CCtx, TL): Unit = val p = lhs.toDnf -> rhs.toDnf - if cctx.cache(p) then log(s"Cached!") - else trace(s"CONSTRAINT ${lhs.showDbg} <: ${rhs.showDbg}"): + if cctx.cache(p) then log(dbg"Cached!") + else trace(dbg"CONSTRAINT ${lhs.showDbg} <: ${rhs.showDbg}"): cctx.nest(p) givenIn: val ty = dnf(inlineSkolemBounds(lhs & rhs.!, true)(using Set.empty)) constrainDNF(ty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index 4614e2518f..5aaba70cb4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -124,13 +124,13 @@ class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL) private def typeAndSubstType (ty: Term, pol: Bool)(using map: Map[Uid[Symbol], TypeArg])(using ctx: InvalCtx, cctx: CCtx) : GeneralType = - trace[GeneralType](s"${ctx.lvl}. Typing type ${ty.showDbg}", r => s"~> ${r.showDbg}"): + trace[GeneralType](dbg"${ctx.lvl}. Typing type ${ty.showDbg}", r => s"~> ${r.showDbg}"): def mono(ty: Term, pol: Bool): Type = monoOrErr(typeAndSubstType(ty, pol), ty) ty match case Capture(base, _) => typeAndSubstType(base, pol) case Ref(sym: LocalSymbol) => - log(s"Type lookup: ${sym.nme} ${sym.uid} ${map.keySet}") + log(dbg"Type lookup: ${sym.nme} ${sym.uid} ${map.keySet}") map.get(sym.uid) match case Some(Wildcard(in, out)) => if pol then out else in case Some(ty: Type) => ty @@ -492,7 +492,7 @@ class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL) // * Note: currently, the returned type is not used or useful, but it could be in the future private def ascribe(lhs: Term, rhs: GeneralType)(using ctx: InvalCtx, scope: Scope): (GeneralType, Type) = - trace[(GeneralType, Type)](s"${ctx.lvl}. Ascribing ${lhs.showDbg} : ${rhs.showDbg}", res => s"! ${res._2.showDbg}"): + trace[(GeneralType, Type)](dbg"${ctx.lvl}. Ascribing ${lhs.showDbg} : ${rhs.showDbg}", res => s"! ${res._2.showDbg}"): given CCtx = CCtx.init(lhs, S(rhs)) (lhs, rhs) match case (Term.Lam(PlainParamList(params), body), ft @ PolyFunType(args, ret, eff)) => // * annoted functions @@ -619,7 +619,7 @@ class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL) }, typeAndSubstType(resTy, true)(using map.toMap), Bot)) private def typeCheck(t: Term)(using ctx: InvalCtx, scope: Scope): (GeneralType, Type) = - trace[(GeneralType, Type)](s"${ctx.lvl}. Typing ${t.showDbg}", res => s": (${res._1.showDbg}, ${res._2.showDbg})"): + trace[(GeneralType, Type)](dbg"${ctx.lvl}. Typing ${t.showDbg}", res => s": (${res._1.showDbg}, ${res._2.showDbg})"): given CCtx = CCtx.init(t, N) t match case Term.Annotated(Annot.Untyped(), _) => (Bot, Bot) diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/NormalForm.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/NormalForm.scala index ae2bb5b3c0..adc7e223f1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/NormalForm.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/NormalForm.scala @@ -150,7 +150,7 @@ sealed abstract class NormalForm extends TypeExt: object NormalForm: def inter(lhs: Disj, rhs: Disj)(using TL): Disj = - tl.traceNot[Disj](s"inter ${lhs.showDbg} and ${rhs.showDbg}", r => s"= ${r.showDbg}"): + tl.traceNot[Disj](dbg"inter ${lhs.showDbg} and ${rhs.showDbg}", r => s"= ${r.showDbg}"): if lhs.isBot || rhs.isBot then Disj.bot else Disj(lhs.conjs.flatMap(lhs => rhs.conjs.flatMap(rhs => lhs.merge(rhs) match { case S(conj) => conj :: Nil @@ -160,7 +160,7 @@ object NormalForm: def union(lhs: Disj, rhs: Disj): Disj = Disj(lhs.conjs ++ rhs.conjs) def neg(ty: Type)(using TL): Disj = - tl.traceNot[Disj](s"~DNF ${ty.showDbg} ${ty.getClass} ${ty.toBasic.showDbg}", r => s"= ${r.showDbg}"): + tl.traceNot[Disj](dbg"~DNF ${ty.showDbg} ${ty.getClass} ${ty.toBasic.showDbg}", r => s"= ${r.showDbg}"): ty match case u: Union => Disj(Conj(Inter(N), u, Nil) :: Nil) case _ => ty.toBasic match @@ -174,7 +174,7 @@ object NormalForm: case NegType(ty) => dnf(ty) def dnf(ty: Type)(using TL): Disj = - tl.traceNot[Disj](s"DNF ${ty.showDbg} ${ty.getClass}", r => s"= ${r.showDbg}"): + tl.traceNot[Disj](dbg"DNF ${ty.showDbg} ${ty.getClass}", r => s"= ${r.showDbg}"): ty match case d: Disj => d case c: Conj => Disj(c :: Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/TypeSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/TypeSimplifier.scala index 40b046bdb9..16fd354183 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/TypeSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/TypeSimplifier.scala @@ -5,6 +5,7 @@ import scala.collection.immutable.{SortedMap, SortedSet} import scala.util.boundary, boundary.break import scala.util.chaining._ +import hkmc2.dbg import hkmc2.utils.*, shorthands.* import hkmc2.utils.* @@ -29,7 +30,7 @@ class TypeSimplifier(tl: TraceLogger): tv.state.upperBounds = tv.state.upperBounds.map(goType) tv def goType(ty: Type): Type = - trace[Type](s"simplifyStructure(${ty.showDbg})", r => s"= ${r.showDbg}"): + trace[Type](dbg"simplifyStructure(${ty.showDbg})", r => s"= ${r.showDbg}"): ty match case tv: InfVar => goTV(tv) case _ => @@ -75,19 +76,19 @@ class TypeSimplifier(tl: TraceLogger): } override def apply(pol: Bool)(ty: GeneralType): Unit = boundary: - trace(s"Analyse[${printPol(pol)}] ${ty.showDbg} [${curPath.reverseIterator.mkString(" ~> ")}]"): + trace(dbg"Analyse[${printPol(pol)}] ${ty.showDbg} [${curPath.reverseIterator.map(_.showDbg).mkString(" ~> ")}]"): ty match case ty if ty.lvl <= lvl => - log(s"Level is < $lvl") + log(dbg"Level is < $lvl") case tv: IV if { occsNum(tv) = occsNum.getOrElse(tv, 0) + 1; false } => case tv: IV => - if varSubst.contains(tv) then break(log(s"Already subst'd")) // * If the IV was set to be substituted, it means it's been found recursive and we don't need to traverse it again + if varSubst.contains(tv) then break(log(dbg"Already subst'd")) // * If the IV was set to be substituted, it means it's been found recursive and we don't need to traverse it again var continue = true // if (!traversedTVs.contains(tv)) { if curPath.exists(_ is tv) then // TODO opt traversedTVs += tv val recPrefix = curPath.takeWhile(_ isnt tv) - log(s"UNIFYING ${tv.showDbg} with ${recPrefix.mkString(", ")}") + log(dbg"UNIFYING ${tv.showDbg} with ${recPrefix.map(_.showDbg).mkString(", ")}") recPrefix.foreach: tv2 => if tv2 isnt tv then traversedTVs += tv2 @@ -102,9 +103,9 @@ class TypeSimplifier(tl: TraceLogger): else if tvRepr.lvl > lvl && !varSubst.contains(tvRepr) then varSubst += tvRepr -> tv continue = false // TODO else?? - if traversedTVs.contains(tv) then log(s"Now already traversed ${tv.showDbg}") + if traversedTVs.contains(tv) then log(dbg"Now already traversed ${tv.showDbg}") else if pastPathsSet.contains(tv) then - log(s"REC ${tv.showDbg}") + log(dbg"REC ${tv.showDbg}") recVars += tv continue = false if continue then @@ -155,7 +156,7 @@ class TypeSimplifier(tl: TraceLogger): pastPathsSet --= oldPath () - trace(s"Simplifying type ${ty.showDbg}"): + trace(dbg"Simplifying type ${ty.showDbg}"): Analysis(pol)(ty) log("Unif-pre: " + Analysis.varSubst) @@ -172,15 +173,15 @@ class TypeSimplifier(tl: TraceLogger): val traversed: MutSet[IV] = MutSet.empty val transformed: MutMap[IV, Type] = MutMap.empty - def subst(ty: GeneralType): GeneralType = trace[GeneralType](s"subst(${ty.showDbg})", r => s"= ${r.showDbg}"): + def subst(ty: GeneralType): GeneralType = trace[GeneralType](dbg"subst(${ty.showDbg})", r => s"= ${r.showDbg}"): ty match case ty if ty.lvl <= lvl => ty // TODO NOPE case _tv: IV => val tv = Analysis.getRepr(_tv) - log(s"Repr: ${tv.showDbg}") + log(dbg"Repr: ${tv.showDbg}") transformed.getOrElseUpdate(tv, { if Analysis.recVars.contains(tv) then - log(s"It's recursive!") + log(dbg"It's recursive!") transformed += tv -> tv else transformed += tv -> // TypeBounds(TopType, BotType)(noProv) // TODO improve? creates lots of junk... diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala index 7004f877de..9b8f5ad29c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala @@ -325,7 +325,7 @@ case class PolyType(tvs: Ls[InfVar], outer: Opt[InfVar], body: GeneralType) exte // * `outer` is already skolemized when it is declared val map = tvs.map(v => val sk = InfVar(lvl, nextUid, new VarState(), true)(v.sym, v.hint) - tl.log(s"skolemize ${v.showDbg} ~> ${sk.showDbg}") + tl.log(dbg"skolemize ${v.showDbg} ~> ${sk.showDbg}") v.uid -> sk ).toMap substAndGetBody(using map) @@ -333,7 +333,7 @@ case class PolyType(tvs: Ls[InfVar], outer: Opt[InfVar], body: GeneralType) exte def instantiate(nextUid: => Uid[InfVar], env: InfVar, lvl: Int)(tl: TL)(using State): GeneralType = val map = (outer.map(_.uid -> env).toList ++ tvs.map(v => val nv = InfVar(lvl, nextUid, new VarState(), false)(new InstSymbol(v.sym), v.hint) - tl.log(s"instantiate ${v.showDbg} ~> ${nv.showDbg}") + tl.log(dbg"instantiate ${v.showDbg} ~> ${nv.showDbg}") v.uid -> nv )).toMap substAndGetBody(using map) diff --git a/hkmc2/shared/src/main/scala/hkmc2/io/FileSystem.scala b/hkmc2/shared/src/main/scala/hkmc2/io/FileSystem.scala index 17368b1678..da1cc73c43 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/io/FileSystem.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/io/FileSystem.scala @@ -18,6 +18,9 @@ trait FileSystem: /** Write string to file, overwriting if exists. */ def write(path: Path, content: String): Unit + /** Append string to file, creating it if missing. */ + def append(path: Path, content: String): Unit + /** Check if a file exists at the given path. */ def exists(path: Path): Bool diff --git a/hkmc2/shared/src/main/scala/hkmc2/io/Path.scala b/hkmc2/shared/src/main/scala/hkmc2/io/Path.scala index 80a72aeeab..3eeda24da8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/io/Path.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/io/Path.scala @@ -45,6 +45,11 @@ object Path: /** Create path from string - delegates to platform-specific implementation */ def apply(str: String): Path = PathFactory.fromString(str) + /** Resolve a path written in source code, such as an import path or a debug output file: + * an absolute path is used as is, and any other path is relative to `base`. */ + def resolve(base: Path, str: String): Path = + if str.startsWith("/") then Path(str) else base / RelPath(str) + /** Platform-specific path separator */ def separator: String = PathFactory.separator diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index b7bd3290f2..93ad1b19da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -727,10 +727,6 @@ object Elaborator: "globalThis" -> globalThisSymbol, )) val superSymbol = builtinOpsMap("super") - def dbg: Bool = false - def dbgUid(uid: Uid[Symbol]): Str = - if dbg then s"‹$uid›" else "" - // ^ we do not display the uid by default to avoid polluting diff-test outputs transparent inline def State(using state: State): State = state /** Extracts all parameter lists from a `constructor(...)...` declaration. @@ -806,6 +802,9 @@ extends Importer: val lang = config.language val newResolution: Bool = lang.useNewResolution + private def debugElaboration[T](annotations: Ls[Annot])(thunk: => T): T = + tl.traceDefinition(annotations, config)(thunk) + lazy val illegalMemberNameTail = msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil @@ -844,6 +843,9 @@ extends Importer: case App(Ident("config"), Tup(args)) => val modify = ConfigParser.parseOverrides(args) S(Annot.Config(modify)(tree.toLoc)) + case App(Ident("dbg"), Tup(args)) => + val modify = ConfigParser.parseDebugDirective(args, definitionLocal = true, wd) + S(Annot.Debug(modify)(tree.toLoc)) case App(Ident("affine"), Tup(IntLit(whichParamList) :: Nil)) => S(Annot.Affine(whichParamList.toInt)(tree.toLoc)) case _ => term(tree, Trm) match @@ -1034,7 +1036,7 @@ extends Importer: Head.Let(binding, term) protected def mkMatch(scrutinee: Term, pattern: Pattern, consequent: SimpleSplit) = - log(s"mkMatch: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, consequent = ${consequent.showDbg}") + log(dbg"mkMatch: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, consequent = ${consequent.showDbg}") matchScrutPat(scrutinee, pattern) // Captures carry the scope crossings needed to resolve extracted bindings, // but matching the value at runtime only needs the underlying reference. @@ -1267,7 +1269,7 @@ extends Importer: // * In the new syntax, only some positions do (see `delimitedTerm`), and placeholders elsewhere are illegal. def term(tree: Tree, interp: Interpretation): Ctxl[Term] = if lang.newUnderscoreAndApplicationSyntax then - trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): + trace[Term](dbg"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): given UnderCtx = UnderCtx.none subterm(tree, interp) else delimitedTerm(tree, interp) @@ -1275,7 +1277,7 @@ extends Importer: /** Elaborate `tree` as a term that delimits an implicit lambda, whose parameters are the `_` placeholders * of `tree` that are not delimited by one of its subterms, as in the body of `=> _ + 1`. */ def delimitedTerm(tree: Tree, interp: Interpretation): Ctxl[Term] = - trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): + trace[Term](dbg"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): val unders = mutable.ArrayBuffer.empty[VarSymbol] given UnderCtx = UnderCtx(S(unders)) val st = subterm(tree, interp) @@ -1407,7 +1409,7 @@ extends Importer: // Most recursive operands are terms. Transparent wrappers must explicitly // forward their interpretation; class, pattern, and type operands specify theirs. def subterm(tree: Tree, interp: Interpretation = Trm): Ctxl[UnderCtx ?=> Term] = - trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): + trace[Term](dbg"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): def error = Term.Error().withLocOf(tree) @@ -1542,7 +1544,7 @@ extends Importer: case (hd @ Hndl(id: Ident, c, Block(sts_), S(bod))) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: val sym = VarSymbol(id, erasedType = N) - log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") + log(dbg"Processing `handle` statement $id (${sym}) ${ctx.outer}") val derivedClsSym = ClassSymbol(Tree.DummyTypeDef(syntax.Cls), Tree.Ident(s"Handler$$${id.name}$$")) recordDefinitionScope(derivedClsSym) @@ -1927,7 +1929,7 @@ extends Importer: case PrefixApp(kw @ Keywrd(Keyword.`return`), body) => ctx.getRetHandler match case ReturnHandler.Required(sym) => - log(s"Non-local return: $sym") + log(dbg"Non-local return: $sym") if !ctx.potentiallyInstrumented then raise: ErrorReport(msg"Non-local return statements are only supported with effect handlers enabled." -> tree.toLoc :: Nil) @@ -2141,7 +2143,7 @@ extends Importer: def blockOrRcd(blk: Block, hasResult: Bool, resultInterp: Interpretation)(using UnderCtx) : Ctxl[(Blk | Rcd, Ctx)] = trace[(Blk | Rcd, Ctx)]( - pre = s"Elab block ${blk.desugStmts.toString.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1.showDbg}" + pre = dbg"Elab block ${blk.desugStmts.showAsPlain.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1.showDbg}" ): val members = blk.definedSymbols.toMap @@ -2176,7 +2178,7 @@ extends Importer: raise(ErrorReport(msg"Multiple definitions of symbol '$name'" -> N :: group.map(msg"defined here" -> _.toLoc))) val mainDefn = group.head // * Safe since these `groupMapReduce` groups cannot be empty - log(s"Processing overloadings for '$name'") + log(dbg"Processing overloadings for '$name'") defns.iterator.foreach: defn => if defn.k > k then // Only function definitions with parameters create a fresh companion object. @@ -2390,7 +2392,7 @@ extends Importer: if tups.isEmpty || id.name.headOption.exists(_.isLower) => val sym = fieldOrVarSym(LetBind, id) - log(s"Processing `let` statement $id (${sym}) ${ctx.outer}") + log(dbg"Processing `let` statement $id (${sym}) ${ctx.outer}") members.get(id.name).foreach: s => raise(ErrorReport(msg"Name '${id.name}' is already used" -> hd.toLoc :: msg"by a member declared in the same block" -> s.toLoc :: Nil)) @@ -2434,7 +2436,6 @@ extends Importer: -> lhs.toLoc :: Nil)) // TODO BE go(sts, Nil, Term.Error().withLocOf(lhs) :: acc)(N) case (td @ TermDef(k, nme, rhs)) :: sts => - log(s"Processing term definition $nme") td.symbName match case S(L(d)) => raise(d) case _ => () @@ -2457,14 +2458,23 @@ extends Importer: msg"Illegal ${k.desc} member name: '${id.name}'" -> nme.toLoc :: illegalMemberNameTail return go(sts, Nil, acc)(N) + val paramLists = td.paramLists match + case pss @ (_ :: _) if k.isInstanceOf[Val] => + raise: + ErrorReport: + msg"${k.desc.capitalize} definitions cannot have parameter lists" -> Loc(pss) + :: Nil + Nil + case pss => pss val isMethod = owner.exists(_.isInstanceOf[ClassSymbol]) val tsym = TermSymbol(k, owner, id) // TODO? // Recorded before the definition's own type parameters enter scope. rctx.state.lexicalTypeBinders(tsym) = ctx.typeBinders - val tdf = ctx.nest(OuterCtx.NonReturnContext(S(tsym))).givenIn: newCtx ?=> + val elaborateDefinition: Ctx ?=> TermDefinition = newCtx ?=> + log(dbg"Processing term definition $nme") // * Add type parameters to context val (tps, newCtx1) = td.typeParams match case S(t) => @@ -2474,7 +2484,7 @@ extends Importer: if newResolution then tps.foreach(ps => registerTypeParameters(tsym, ps.map(_.sym))) // * Add parameters to context var newCtx = newCtx1 - val pss = td.paramLists.map: ps => + val pss = paramLists.map: ps => val (res, newCtx2) = funParams(ps)(using newCtx) newCtx = newCtx2 res @@ -2531,6 +2541,9 @@ extends Importer: tdf + val tdf = debugElaboration(annotations): + ctx.nest(OuterCtx.NonReturnContext(S(tsym))).givenIn(elaborateDefinition) + go(sts, Nil, tdf :: acc)(N) case L(d) => reportUnusedAnnotations @@ -2601,16 +2614,17 @@ extends Importer: case Annot.Modifier(Keyword.`data`) => true case _ => false - val pss = td.paramLists.map: ps => - val (res, newCtx2) = - given Ctx = newCtx - params(ps, isDataClass, k is Pat) - newCtx = newCtx2 - // Spread parameters are not supported in class parameters. - res.restParam.foreach: rp => - raise(ErrorReport( - msg"Spread parameters are not supported in class parameters." -> rp.toLoc :: Nil)) - res.copy(restParam = N)(res.toLoc) + val pss = debugElaboration(annotations): + td.paramLists.map: ps => + val (res, newCtx2) = + given Ctx = newCtx + params(ps, isDataClass, k is Pat) + newCtx = newCtx2 + // Spread parameters are not supported in class parameters. + res.restParam.foreach: rp => + raise(ErrorReport( + msg"Spread parameters are not supported in class parameters." -> rp.toLoc :: Nil)) + res.copy(restParam = N)(res.toLoc) def withFields(extraParams: Ls[ParamList])(using Ctx)(fn: (Ctx) ?=> (Term.Blk, Ctx)): (Term.Blk, Ctx) = softAssert(pss.sizeCompare(td.clsParams) === 0, @@ -2717,7 +2731,7 @@ extends Importer: msg"Illegal body of ${k.desc} definition (should be a block; found ${t.describe})." -> t.toLoc :: Nil)) (new Blk(Nil, Term.Lit(UnitLit(false))), ctx) - val defn = k match + val defn = debugElaboration(annotations)(k match case Als => val alsSym = td.symbol.asInstanceOf[TypeAliasSymbol] // TODO improve `asInstanceOf` // newCtx.nest(S(alsSym)).givenIn: @@ -2754,8 +2768,8 @@ extends Importer: // 2. extraction bindings, e.g., `value` in `pattern Middle(value) = ...`; and // 3. the rest are reported as invalid parameters. val (patternParams, extractionParams) = allParams.partition(_.flags.pat) - log(s"`${patSym.nme}`'s pattern parameters: ${patternParams.mkString("[", ", ", "]")}") - log(s"`${patSym.nme}`'s extraction parameters: ${extractionParams.mkString("[", ", ", "]")}") + log(dbg"`${patSym.nme}`'s pattern parameters: ${patternParams.map(_.showDbg).mkString("[", ", ", "]")}") + log(dbg"`${patSym.nme}`'s extraction parameters: ${extractionParams.map(_.showDbg).mkString("[", ", ", "]")}") // Empty pattern body is considered as wildcard patterns. val rhs = td.rhs.getOrElse: raise(ErrorReport(msg"Pattern definitions must have a body." -> statementLoc :: Nil)) @@ -2777,8 +2791,8 @@ extends Importer: case N if !guardedNames.contains(name) => raise(WarningReport(msg"Unused pattern binding: $name." -> aliases.head.toLoc :: Nil)) case _ => () - scoped("ucs:ups")(log(s"elaborated pattern body: ${pat.showDbg}")) - scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) + scoped("ucs:ups")(log(dbg"elaborated pattern body: ${pat.showDbg}")) + scoped("ucs:ups:tree")(log(dbg"elaborated pattern body: ${pat.showAsTree}")) // `paramsOpt` is set to `N` because we don't want parameters to // appear in the generated class's constructor. val pd = PatternDef(owner, patSym, sym, tps, allParams, @@ -2789,13 +2803,13 @@ extends Importer: case k: (Mod.type | Obj.type) => val modSym = td.symbol.asInstanceOf[ModuleOrObjectSymbol] // TODO: improve `asInstanceOf` newCtx.givenIn: - trace(s"Processing module/object definition $nme"): + trace(dbg"Processing module/object definition $nme"): val comp = sym.asCls match case comp @ S(_) => assert(sym.asAls.isEmpty) comp case N => sym.asAls - log(s"Companion: ${comp}") + log(dbg"Companion: ${comp}") val md = val (bod, c) = mkBody(Nil) ModuleOrObjectDef(owner, modSym, sym, @@ -2833,9 +2847,9 @@ extends Importer: msg"Spread parameters are not supported in class parameters." -> rp.toLoc :: Nil)) res.copy(restParam = N)(res.toLoc) newCtx.givenIn: - trace(s"Processing class definition $nme"): + trace(dbg"Processing class definition $nme"): val comp = sym.asMod - log(s"Companion: ${comp}") + log(dbg"Companion: ${comp}") val allCtorPss = pss ::: auxCtorPss val tsym = if allCtorPss.nonEmpty then val ctsym = ClassCtorSymbol(Fun, owner, clsSym) @@ -2867,6 +2881,7 @@ extends Importer: clsSym.defn = S(cd) cd case Trt | Mxn => lastWords(s"Unexpected type definition kind here: $k") + ) go(sts, Nil, defn :: acc)(N) case Annotated(annotation, target) :: sts => go(target :: sts, annotations ++ annot(annotation), acc)(statementLoc) @@ -2882,6 +2897,10 @@ extends Importer: reportUnusedAnnotations val modify = ConfigParser.parseLanguageDirective(args) go(sts, Nil, SetConfig(modify)(statementLoc) :: acc)(N) + case Directive(Ident("dbg"), Tup(args)) :: sts => + reportUnusedAnnotations + val modify = ConfigParser.parseDebugDirective(args, definitionLocal = false, wd) + go(sts, Nil, SetConfig(modify)(statementLoc) :: acc)(N) case Directive(Ident(name), _) :: sts => raise(ErrorReport( msg"Unknown directive '#${name}'" -> sts.headOption.flatMap(_.toLoc) :: Nil, @@ -2991,7 +3010,7 @@ extends Importer: def param(t: Tree, inUsing: Bool, inDataClass: Bool): Ctxl[Diagnostic \/ (Param, Opt[SpreadKind], Ls[Str])] = t.desugared.asParam(inUsing).map: case pt @ ParamTree(flags, id, sign, spd, modifiers) => - log(s"Elaborating ParamTree: ${pt}") + log(dbg"Elaborating ParamTree: ${pt}") val flg = flags.copy(isVal = flags.isVal || inDataClass) val (canonicalId, aliases) = symbolicSuffixBase(id.name) match case S(base) => @@ -3109,7 +3128,7 @@ extends Importer: * FIXME: This routine is insufficient to look up definitions defined * later in the program. */ def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = scoped("ucs:pattern:resolution"): - log(s"resolve ${id}") + log(dbg"resolve ${id}") ctx.get(id.name) match case S(elem) => log("has elem!") @@ -3199,7 +3218,7 @@ extends Importer: def arg(t: Tree): Ctxl[Pattern \/ Pattern] = t match case TypeDef(syntax.Pat, body, N) => L(go(body)) case _ => R(go(t)) - def go(t: Tree): Ctxl[Pattern] = trace[Pattern](s"Elab pattern ${t.showDbg}", r => s"~> ${r.showDbg}"): + def go(t: Tree): Ctxl[Pattern] = trace[Pattern](dbg"Elab pattern ${t.showDbg}", r => s"~> ${r.showDbg}"): t match // Annotated patterns like `@compile P`. case Tree.Annotated(annotation, target) => @@ -3390,7 +3409,7 @@ extends Importer: TODO(sym->sym.uid) // case S(sym) => ??? case N => - log(s"No symbol found $lhs ${lhs.symbol}") + log(dbg"No symbol found $lhs ${lhs.symbol}") // ??? () // TODO case Term.Ref(sym: VarSymbol) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index abaec6fc38..f2ded6d33d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -23,10 +23,7 @@ class Importer extends NewResolver: // log(s"pwd: ${os.pwd}") // log(s"wd: ${wd}") - val file = - if path.startsWith("/") - then io.Path(path) - else wd / io.RelPath(path) + val file = io.Path.resolve(wd, path) val nme = file.baseName val id = alias.getOrElse(new syntax.Tree.Ident(nme)) // TODO loc @@ -34,7 +31,7 @@ class Importer extends NewResolver: lazy val sym = VarSymbol(id, erasedType = N) if path.startsWith(".") || path.startsWith("/") then // leave alone imports like "fs" - log(s"importing $file") + log(dbg"importing $file") val nme = file.baseName val id = new syntax.Tree.Ident(nme) // TODO loc @@ -57,7 +54,7 @@ class Importer extends NewResolver: reportCycle(cctx.allFilesBeingImported :+ file) else cctx.withActiveDependency(file)(reportCycle): - val importedSym = tl.trace(s">>> Importing $file"): + val importedSym = tl.trace(dbg">>> Importing $file"): given TL = tl val artifact = cctx.getElaboratedBlock(file, prelude) artifact.compilationUnit.defaultExport.getOrElse: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index b233d24c41..f99569380f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1624,7 +1624,7 @@ class NewResolver: // consumes a candidate's entry site and the new entry supplies its own. // Deferred references use exactly the same transport as ordinary values. // The nested trace shows the carrier standing in for `base` (see markCarrier). - trace[DeclaredType](s"transportType ${base.resolution.showDbg} (previous = ${previous.map(_.showDbg)}, marks = ${marks.map(_.showDbg)})"): + trace[DeclaredType](dbg"transportType ${base.resolution.showDbg} (previous = ${previous.map(_.showDbg)}, marks = ${marks.map(_.showDbg)})"): markCarrier.exit(previous).exit(marks) match case Marked(_, NoMarks) => base case Marked(_, path: SomeMarks) => @@ -2956,16 +2956,16 @@ class NewResolver: return case _ => () val sh = appShapes.getOrElseUpdate((ShapeIdentity.key(lhs), res.resSym), { - log(s"appShape: lhs = ${lhs.shwDbg}, args = ${args.showDbg}, res = ${res.showDbg}") + log(dbg"appShape: lhs = ${lhs.shwDbg}, args = ${args.showDbg}, res = ${res.showDbg}") new AppShape(lhs, args, res) }) lhs.unappliedParams match case Nil => () case (ps, mss) :: pss => zipArgsIn(mss, ps.params, ps.restParam, args, res, lhs, instances) - log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead._1.shwDbg}, ${sh.applicationHead._2.map(_.showDbg)}") + log(dbg"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead._1.shwDbg}, ${sh.applicationHead._2.map(_.showDbg)}") def register(using ResolutionContext) = publish(sh) - log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") + log(dbg"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") if lhs.isSaturated && !rctx.hasError(res) then rctx.markError(res) // Marks transport a value across scopes; they do not apply arguments. @@ -3000,7 +3000,7 @@ class NewResolver: // TODO: handle `mss` register case _ => - log(s"appShape: td.body = ${td.body}") + log(dbg"appShape: td.body = ${td.body}") listenDefinitionResult(td, instances): result => transportShape(result, mss) match case value: TermShape => publish(value) @@ -3248,13 +3248,13 @@ class NewResolver: /** `scope` is where `sel` is written, which determines the private members it can select. */ def newSel(sel: NewSel, scope: SrcScope)(using ResolutionContext): Unit = - log(s"newSel? sel = ${sel.showDbg}") + log(dbg"newSel? sel = ${sel.showDbg}") def visible(info: MemberLookup): MemberLookup = info.visibleFrom(scope.canAccessPrivatesOf, reportInaccessible = true) def member(info: MemberLookup, description: Message, diagnostic: Message => Ls[(Message, Opt[Loc])], instances: TypeSubstitution)(using ResolutionContext): Unit = info match case MemberLookup.Contextual(source, substitution) => member(source, description, diagnostic, instances.withOverrides(substitution)) case MemberLookup.Found(bms, marks) if rctx.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => - log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") + log(dbg"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rctx.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) publishMember(sel, bms, sel.resSym, marks, instances) case MemberLookup.Declared(member, bindings, marks, annotation, positive) if rctx.canResolve(sel) || sel.resolvedMembers.contains(member) => @@ -3298,7 +3298,7 @@ class NewResolver: , shape => member(visible(shape.getMember(sel.id.name)), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty)) case N => listenReceiver(sel.prefix): shape => - log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") + log(dbg"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(visible(shape.getMember(sel.id.name)), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) case S(cls) => classSelection(cls)(ref => @@ -3452,7 +3452,7 @@ class NewResolver: case N => listenResult(body)(publishActivated(host, _)) def pipeTerm(from: Term, to: ShapeHost)(using ResolutionContext): Unit = - log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") + log(dbg"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") listenTerm(from): sh => publishActivated(to, sh) @@ -3576,9 +3576,9 @@ class NewResolver: private def fromBMSAt(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(using ResolutionContext): Unit = - log(s"listenBMS: bms = ${bms.describe}") + log(dbg"listenBMS: bms = ${bms.describe}") bms.onComplete: () => - log(s"listenedBMS: bms = ${bms.describe}") + log(dbg"listenedBMS: bms = ${bms.describe}") valueTarget(bms, receiver) match case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol | PatternSymbol)) => // Selection is independent of the selected value's shape. In particular, @@ -3586,7 +3586,7 @@ class NewResolver: // Pattern resolution supplies its own interpretation of the selected head. selected(sym) val wrappedListener: Listener = sh => - log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") + log(dbg"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh // Modules, objects, and runtime pattern objects introduce no value // boundary. Pattern bodies have separate matching scopes; passing the @@ -3599,15 +3599,15 @@ class NewResolver: case value: TermShape => transportShape(value, markss) case NoShape => NoShape) match case NoShape => - log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") + log(dbg"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") case sh: TermShape => if sh0.isInstanceOf[MarkedShape[?]] - then log(s"MATCH ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") - else log(s"PUSH ${sh.shwDbg}") + then log(dbg"MATCH ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") + else log(dbg"PUSH ${sh.shwDbg}") listener(sh) sym.defn match case S(td: TermDefinition) if td.params.isEmpty => - log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") + log(dbg"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") val callerInstances = rctx.instances val instances = if isByName(td) then instantiateByName(td, resSym, ExitMark(ResolutionBoundary(td.tsym), S(resSym), NoMarks) :: markss, specialization, Map.empty) @@ -3808,7 +3808,7 @@ class NewResolver: flow.exits.getOrElse(N, Vector.empty).foreach(value => listenTerm(value)(listener)) def listenTerm(trm: Term)(listener: Listener)(using ResolutionContext): Unit = - log(s"listenTerm: trm = ${trm.showDbg}") + log(dbg"listenTerm: trm = ${trm.showDbg}") listenValue(trm, false)(listener) def listenReceiver(trm: Term)(listener: Listener)(using ResolutionContext): Unit = @@ -3871,7 +3871,7 @@ class NewResolver: private def listen(trm: Term, discardMarks: Bool)(receive: ShapeListener[Shape])(using ResolutionContext): Unit = val listener = activationListener(receive) - log(s"listen: trm = ${trm.showDbg}") + log(dbg"listen: trm = ${trm.showDbg}") trm.addShapeListener(listener) trm match case _: SynthSel => @@ -4067,7 +4067,7 @@ class NewResolver: case _ => N def publish(parent: Opt[NominalInstanceView])(using ResolutionContext): Unit = listener(introShapes.getOrElseUpdate(new Identity(intro), { - log(s"introShape: intro = $intro") + log(dbg"introShape: intro = $intro") IntroShape(intro, parent) })) primitive match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index 4fcf7ffaba..ddc1fe3b8f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -259,7 +259,7 @@ class Resolver(tl: TraceLogger) * usually the entry point for the resolver. */ def traverseBlock(blk: Term.Blk)(using ICtx): ICtx = - trace(s"Traversing block: $blk"): + trace(dbg"Traversing block: $blk"): traverseStmts(blk.stats).givenIn: traverse(blk.res, expect = Any) ictx @@ -268,7 +268,7 @@ class Resolver(tl: TraceLogger) final def traverseStmts(stmts: Ls[Statement])(using ICtx): ICtx = stmts match case Nil => ictx case stmt :: rest => - log(s"Traversing statement: $stmt") + log(dbg"Traversing statement: $stmt") val newICtx = stmt match case tdf: Definition => traverseDefn(tdf) @@ -289,7 +289,7 @@ class Resolver(tl: TraceLogger) * check the modulefulness of the term. */ def traverse(t: Term, expect: Expect)(using ictx: ICtx): Unit = - trace(s"Traversing term: $t (expect = ${expect})"): + trace(dbg"Traversing term: $t (expect = ${expect})"): t match case blk: Term.Blk => @@ -341,7 +341,7 @@ class Resolver(tl: TraceLogger) if !cfg.noModuleCheck then val evalsToModule = ModuleChecker.evalsToModule(t, prefer = expect) val evalsToStaticClass = ModuleChecker.isStaticClass(t) - log(s"Checking ${t}: expect ${expect}, evalsToModule = ${evalsToModule}, evalsToStaticClass = ${evalsToStaticClass}") + log(dbg"Checking ${t}: expect ${expect}, evalsToModule = ${evalsToModule}, evalsToStaticClass = ${evalsToStaticClass}") if expect.`module` && !evalsToModule then raise(ErrorReport(msg"Expected a module; found non-moduleful ${t.describe}." -> t.toLoc @@ -357,7 +357,7 @@ class Resolver(tl: TraceLogger) end traverse def traverseDefn(defn: Definition)(using ICtx): ICtx = - trace(s"Resolving definition: $defn"): + trace(dbg"Resolving definition: $defn"): def traverseTermDef(tdf: TermDefinition) = val TermDefinition(_k, _sym, _tsym, pss, tps, sign, body, TermDefFlags(isMethod, _), modulefulness, annotations, comp @@ -425,7 +425,7 @@ class Resolver(tl: TraceLogger) // Case: instance definition. Add the instance to the context. case defn @ TermDefinition(k = Ins, sym = sym, tsym = tsym, flags = TermDefFlags(isMethod, _), sign = sign) => softAssert(defn.owner.isEmpty) - log(s"Resolving instance definition ${defn.showDbg}") + log(dbg"Resolving instance definition ${defn.showDbg}") traverseTermDef(defn) sign match case N => @@ -436,7 +436,7 @@ class Resolver(tl: TraceLogger) // Case: Fun/Val definition. case defn @ TermDefinition(k = Fun | ImmutVal | MutVal) => - log(s"Resolving ${defn.k.desc} definition $defn") + log(dbg"Resolving ${defn.k.desc} definition $defn") traverseTermDef(defn) ictx @@ -444,7 +444,7 @@ class Resolver(tl: TraceLogger) // context and use the context to traverse through the body. // Traverse through other subterms with original context. case defn: ClassLikeDef => - log(s"Resolving ${defn.kind.desc} definition $defn") + log(dbg"Resolving ${defn.kind.desc} definition $defn") defn match case defn: PatternDef => @@ -590,7 +590,7 @@ class Resolver(tl: TraceLogger) (N, ictx) t.expandedResolvableIn: t => - log(s"Resolving resolvable term ${t} with sym = ${t.legacyResolvedSym}, typ = ${t.resolvedTyp}") + log(dbg"Resolving resolvable term ${t} with sym = ${t.legacyResolvedSym}, typ = ${t.resolvedTyp}") // Fill the context with possibly the type arguments information. val newICtx2 = newICtx1.givenIn: @@ -609,11 +609,11 @@ class Resolver(tl: TraceLogger) case (ictx, (tparam, targ)) => val sym = tparam.sym val typ = resolveSign(targ, expect = Any) - log(s"Resolving App with type arg ${sym} = $typ") + log(dbg"Resolving App with type arg ${sym} = $typ") ictx.withTypeArg(sym, typ) case (S(tparams), N) => tparams.foldLeft(ictx): case (ictx, tparam) => - log(s"Resolving App with type arg ${tparam.sym} unspecified") + log(dbg"Resolving App with type arg ${tparam.sym} unspecified") ictx case (N, _) => ictx newICtx @@ -868,15 +868,15 @@ class Resolver(tl: TraceLogger) case _ if t.symbol.exists(_.isInstanceOf[ErrorSymbol]) => () case t @ Term.Ref(bsym: BlockMemberSymbol) => - log(s"Resolving symbol for reference ${t} (bsym = ${bsym})") + log(dbg"Resolving symbol for reference ${t} (bsym = ${bsym})") val sym = disambSym(prefer, sign = sign)(bsym) sym.foreach: sym => t.expand(S((t.duplicate.resolved(sym)))) resolveType(t, prefer = prefer) - log(s"Resolved symbol for ${t}: ${sym}") + log(dbg"Resolved symbol for ${t}: ${sym}") case t @ AnySel(lhs: Resolvable, id, cls) => lhs.expandedResolvableIn: lhs => - log(s"Resolving symbol for selection ${t}, defn = ${lhs.defn}") + log(dbg"Resolving symbol for selection ${t}, defn = ${lhs.defn}") val clsDefn = cls.flatMap: sym => sym.legacyResolvedSym.flatMap(_.asCls).map: clsSym => @@ -896,15 +896,15 @@ class Resolver(tl: TraceLogger) val fsym = defn.body.members.get(id.name) fsym match case S(bms: BlockMemberSymbol) => - log(s"Resolving symbol for ${t}, defn = ${lhs.defn}") + log(dbg"Resolving symbol for ${t}, defn = ${lhs.defn}") disambSym(prefer, sign)(bms) match case S(ds) => checkPrivateAccess(t, ds) t.expand(S(t.withSym(bms).resolved(ds))) case N => - log(s"Unable to disambiguate ${bms}") + log(dbg"Unable to disambiguate ${bms}") t.expand(S(t.withSym(bms))) - log(s"Resolved symbol for ${t}: ${bms}") + log(dbg"Resolved symbol for ${t}: ${bms}") case N => defn match case defn: ModuleOrObjectDef => @@ -919,7 +919,7 @@ class Resolver(tl: TraceLogger) // - MLscript programs selecting JS members without properly defining them. // - Inherited members. case defn => - log(s"Unsupported selection from definition: ${defn}") + log(dbg"Unsupported selection from definition: ${defn}") case t @ Term.New(cls, _, N) => // cls is already resolved, so the symbol should be present; @@ -966,14 +966,14 @@ class Resolver(tl: TraceLogger) val decl = base.legacyResolvedSym match case S(bms: BlockMemberSymbol) => val disambBms = disambSym(bms) - log(s"Disambiguate ${bms} (${bms.asTrm}) into ${disambBms} (defn = ${disambBms.map(_.defn)}), preferring ${prefer}") + log(dbg"Disambiguate ${bms} (${bms.asTrm}) into ${disambBms} (defn = ${disambBms.map(_.defn)}), preferring ${prefer}") disambBms match case S(disambBms) => disambBms.defn case N => bms.asPrincipal.flatMap(_.defn) case S(bls: LocalVarSymbol) => bls.decl case S(ds: DefinitionSymbol[?]) => ds.defn case _ => N - log(s"Declaration: ${decl}") + log(dbg"Declaration: ${decl}") decl match case S(defn: TermDefinition) if defn.params.length === ass.length => val ty = defn.sign.map(resolveSign(_, expect = Expect.fromModulefulness(defn.modulefulness))) @@ -994,15 +994,15 @@ class Resolver(tl: TraceLogger) end resolveType def resolveArg(p: Param)(lhs: Term)(using ictx: ICtx): Elem = - log(s"Resolving implicit argument, expecting a ${p.sign}") + log(dbg"Resolving implicit argument, expecting a ${p.sign}") p.sign match case S(sign) => // FIXME[Harry]: Don't re-resolve signatures for every argument! val ty = resolveSign(sign, expect = if p.modulefulness.modified then Module(N) else NonModule(N)) - log(s"Resolving implicit argument, expecting a ${ty.show}") + log(dbg"Resolving implicit argument, expecting a ${ty.show}") ictx.query(ty) match case R(i) => - log(s"Resolved ${p.sign} with instance ${i}") + log(dbg"Resolved ${p.sign} with instance ${i}") val ref = i.sym.ref() traverse(ref, // note: we accept regular arguments for module parameters @@ -1022,7 +1022,7 @@ class Resolver(tl: TraceLogger) lastWords(s"No type signature for contextual parameter ${p.showDbg} at ${p.toLoc}") def traverseParam(p: Param)(using ictx: ICtx): Unit = - log(s"Resolving parameter ${p.showDbg}") + log(dbg"Resolving parameter ${p.showDbg}") val ty = p.sign.map(sign => resolveSign(sign, expect = if p.modulefulness.modified then Module(N) else NonModule(N))) p.signType = ty @@ -1048,7 +1048,7 @@ class Resolver(tl: TraceLogger) * details. */ def traverseSign(t: Term, expect: Expect, inAppPrefix: Bool = false)(using ictx: ICtx): Unit = boundary: - trace(s"Traversing type ${t}, expecting ${expect}"): + trace(dbg"Traversing type ${t}, expecting ${expect}"): // * Traverse through the sub-terms. t match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index be319e7bfe..f1ed9ad6f2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -313,7 +313,7 @@ object MarkedShape: case sh: NonMarkedShape => MarkedShape(sh, EntryMark(boundary, id, NoMarks)) case MarkedShape(sh, marks) => MarkedShape(sh, EntryMark(boundary, id, marks)) def exit(sh: TermShape, boundary: ResolutionBoundary, id: Opt[FlowSymbol])(using TL): TermShape | NoShape = - tl.trace[TermShape | NoShape](s".exit MarkedShape (${sh.shwDbg}, ${boundary.showDbg}, ${id.fold("")(_.showDbg)})", res => s"= ${res.shwDbg}"): + tl.trace[TermShape | NoShape](dbg".exit MarkedShape (${sh.shwDbg}, ${boundary.showDbg}, ${id.fold("")(_.showDbg)})", res => s"= ${res.shwDbg}"): sh match // Exits are only ever prepended to a path's trailing exits, never compared // against them; they locate what a value refers to. A self-contained value @@ -404,7 +404,7 @@ sealed trait TermShape extends Shape, ShapeLike: case _ => unappliedParamLists.map(_ -> applicationMarks) def exit(marks: Marks)(using TL): TermShape | NoShape = - tl.trace[TermShape | NoShape](s".exit $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): + tl.trace[TermShape | NoShape](dbg".exit $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): marks match case NoMarks => this case EntryMark(sym, id, rest) => @@ -422,7 +422,7 @@ sealed trait TermShape extends Shape, ShapeLike: exit(mark).exit(rest) def enter(marks: Marks)(using tl: TL): TermShape | NoShape = - tl.trace[TermShape | NoShape](s".enter $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): + tl.trace[TermShape | NoShape](dbg".enter $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): marks match case NoMarks => this case EntryMark(sym, id, rest) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index d809625227..d4f1bfe2ec 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -47,10 +47,10 @@ abstract class Symbol(using ownerState: State) extends MaybeSymbol with Located: def showPrefix(using Scope, ShowCfg, Raise): Str = prefix def showFullName(using Scope, ShowCfg, Raise): Str = - showPrefix + showName + State.dbgUid(uid) + showPrefix + showName override def toString: Str = - prefix + nme + State.dbgUid(uid) + prefix + nme private[semantics] val directRefs: mutable.Buffer[Term.Ref] = mutable.Buffer.empty def ref(id: Tree.Ident = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SymbolPrinter.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SymbolPrinter.scala index 7a710b2182..5d24979fdd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/SymbolPrinter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SymbolPrinter.scala @@ -6,13 +6,10 @@ import syntax.* import hkmc2.utils.* -class SymbolPrinter(val dbgScp: Scope) extends DebugPrinter: +class SymbolPrinter(val dbgScp: Scope)(using owner: Elaborator.State) extends DebugPrinter(S(owner)): + override protected def symbolName(sym: Symbol): Str = + sym.showFullName(using dbgScp, ShowCfg.internal, throw _) - override def printPlain(v: Any): Str = v match - case sym: Symbol => - sym.showFullName(using dbgScp, semantics.ShowCfg.internal, throw _) - case _ => super.printPlain(v) - def printSymbol(sym: Symbol)(using Raise, ShowCfg): Str = sym.showName(using dbgScp, summon) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index e90c716fc4..053024dc9e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -42,6 +42,7 @@ enum Annot extends Located: // Buffered classes expose generated allocation and access methods on the class value. case Bufferable(keepInstances: Bool)(val toLoc: Opt[Loc]) case Config(modify: hkmc2.Config => hkmc2.Config)(val toLoc: Opt[Loc]) + case Debug(modify: hkmc2.Config => hkmc2.Config)(val toLoc: Opt[Loc]) // Marks if a function or lambda is one-shot, i.e. called at most once. // Functions with multiple parameter lists are considered here as a chain of // function values. `whichParamList` is the zero-based index of the parameter @@ -61,7 +62,7 @@ enum Annot extends Located: def subTerms: Vector[Term] = this match case Trm(trm) => Vector.single(trm) case _: Modifier | Untyped() | TailRec() | TailCall() | Inline() | NoInline() - | Generator() | Async() | RaiseEffects() | Pure() | _: Bufferable | _: Config | _: Affine => Vector.empty + | Generator() | Async() | RaiseEffects() | Pure() | _: Bufferable | _: Config | _: Debug | _: Affine => Vector.empty def show(using Scope, ShowCfg, Raise): Document = this match case Untyped() => doc"@untyped" @@ -78,6 +79,7 @@ enum Annot extends Located: case Bufferable(keepInstances) => if keepInstances then doc"@bufferable" else doc"@buffered" case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" + case Debug(_) => doc"@dbg(...)" def mkClone(using State, Erasure): Annot = this match case Trm(trm) => Trm(trm.mkClone)(toLoc) @@ -1267,13 +1269,13 @@ final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol)(val to var isCovariant: Bool = true var isContravariant: Bool = true - def showDbg: Str = + def showDbg(using DebugPrinter): Str = // (if isContravariant then "in " else "") + // (if isCovariant then "out " else "") + (if isCovariant then if isContravariant then "" else "out " else if isContravariant then "in " else "in out ") + - "‹" + flags.show + "›" + sym + "‹" + flags.show + "›" + sym.showDbg object Param: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala index 028c4cbdc5..bdcce84824 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -183,7 +183,7 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: r.legacyResolvedSym.map(s => s"sym=${s.showAsPlain}") :: r.resolvedTyp.map(s => s"typ=${s.showDbg}") :: Nil ).flatten.mkString(",") - case r: SelProj => r.symbol.mkString + case r: SelProj => r.symbol.map(_.showDbg).mkString case _ => "" def subStatements: Vector[Statement] = this match @@ -463,10 +463,10 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: def showDbg(using DebugPrinter): Str = this match case r: Ref => r.sym.showAsPlain case r: Resolved => - s"${r.showPlain}‹${r.sym}›" + s"${r.showPlain}‹${r.sym.showDbg}›" case trm: Term => // s"$showPlain‹${trm.symbol.getOrElse("")}›" - s"$showPlain${trm.symbol.fold("")("‹"+_+"›")}" + s"$showPlain${trm.symbol.fold("")(sym => "‹"+sym.showDbg+"›")}" case _ => showPlain @@ -496,10 +496,11 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" case FunTy(lhs, rhs, eff) => s"(...${lhs.showDbg}) ->${eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" - case TyApp(lhs, targs) => s"${lhs.showDbg}[${targs.mkString(", ")}]" - case Forall(tvs, outer, body) => s"forall ${tvs.mkString(", ")}${outer.map(v => s", outer $v").mkString}: ${body.toString}" + case TyApp(lhs, targs) => s"${lhs.showDbg}[${targs.map(_.showDbg).mkString(", ")}]" + case Forall(tvs, outer, body) => + s"forall ${tvs.map(_.showAsPlain).mkString(", ")}${outer.map(v => s", outer ${v.showDbg}").mkString}: ${body.showDbg}" case Constrained(constraints, body) => s"[${constraints.map(_.showDbg).mkString(", ")}] => ${body.showDbg}" - case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" + case WildcardTy(in, out) => s"in ${in.map(_.showDbg).getOrElse("⊥")} out ${out.map(_.showDbg).getOrElse("⊤")}" case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" case Super(_, owner, id) => s"super[${owner.showDbg}].${id.name}" @@ -522,17 +523,17 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: case DynNew(cls, args) => s"new! ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}" case SelProj(pre, cls, proj) => s"${pre.showDbg}.${cls.showDbg}#${proj.name}" - case Asc(term, ty) => s"${term.toString}: ${ty.toString}" - case LetDecl(sym, _) => s"let ${sym}" - case DefineVar(sym, rhs) => s"${sym} = ${rhs.showDbg}" + case Asc(term, ty) => s"${term.showDbg}: ${ty.showDbg}" + case LetDecl(sym, _) => s"let ${sym.showDbg}" + case DefineVar(sym, rhs) => s"${sym.showDbg} = ${rhs.showDbg}" case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => - s"handle ${lhs} = ${rhs}(${args.mkString(", ")}) ${defs} in ${bod}" + s"handle ${lhs.showDbg} = ${rhs.showDbg}(${args.map(_.showDbg).mkString(", ")}) ${defs.showAsPlain} in ${bod.showDbg}" case Region(name, body) => s"region ${name.nme} in ${body.showDbg}" case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}" case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" case SetRef(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" - case Drop(term) => s"drop $term" - case Deref(term) => s"!$term" + case Drop(term) => s"drop ${term.showDbg}" + case Deref(term) => s"!${term.showDbg}" case Neg(ty) => s"~${ty.showDbg}" case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}" case Error() => "" @@ -540,7 +541,7 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: case Mut(und) => s"mut ${und.showDbg}" case CtxTup(fields) => fields.map(_.showDbg).mkString("‹using›[", ", ", "]") case TermDefinition(k, sym, tsym, pss, tps, sign, body, flags, _, _, _) => - s"${flags.showDbg}${k.str} ${sym}${ + s"${flags.showDbg}${k.str} ${sym.showDbg}${ tps.map(_.map(_.showDbg)).mkStringOr(", ", "[", "]") }${ pss.map(_.showDbg).mkString("") @@ -550,11 +551,11 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: case N => "" }" case cls: ClassLikeDef => - s"${cls.kind} ${cls.sym.nme}${ + s"${cls.kind} ${cls.sym.showDbg}${ cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ - cls.paramsOpt.fold("")(_.toString)} ${cls.body}" + cls.paramsOpt.fold("")(_.showDbg)} ${cls.body.blk.showDbg}" case Import(sym, str, file) => s"import $str from ${file}" - case Annotated(ann, target) => s"@${ann} ${target.showDbg}" + case Annotated(ann, target) => s"@${ann.showAsPlain} ${target.showDbg}" case Throw(res) => s"throw ${res.showDbg}" case Label(label, _, body, _) => s"do ${label.nme}: ${body.showDbg}" case Break(label, _, value) => s"${label.nme}.break${value.fold("")(v => s" ${v.showDbg}")}" @@ -562,7 +563,7 @@ trait StatementImpl extends Located, ProductWithExtraInfo, Describable: case Try(body, finallyDo) => s"try ${body.showDbg} finally ${finallyDo.showDbg}" case Ret(res) => s"return ${res.showDbg}" case TypeDef(sym, _, tparams, rhs, _, _) => - s"type ${sym}${tparams.mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" + s"type ${sym.showDbg}${tparams.map(_.showDbg).mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" case Missing => "missing" case LeadingDotSel(nme) => s"_?_.${nme.name}" case SetConfig(_) => "#config(...)" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 72da6b44ac..7133a02075 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -62,7 +62,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): typeProdImpl(t.expanded, insideSelAppChain) def typeProdImpl(t: Term, insideSelAppChain: Boolean): Producer = - trace[P](s"Typing producer: ${t.showDbg}", post = res => s": ${res.showDbg}"): + trace[P](dbg"Typing producer: ${t.showDbg}", post = res => s": ${res.showDbg}"): def constrain(lhs: P, rhs: C): Unit = collectedConstraints += ((src = t, c = Constraint(lhs, rhs))) @@ -70,7 +70,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): t.ldsRoot match case S(lds) if !insideSelAppChain => val sym = FlowSymbol("bind") - log(s"Constraining leading dot selection ${lds.showDbg} at the top level") + log(dbg"Constraining leading dot selection ${lds.showDbg} at the top level") constrain(P.LeadingDotSel(lds), C.Flow(sym)) constrain(res(typeProd(sub, insideSelAppChain = true)), C.Flow(sym)) P.Flow(sym) @@ -100,7 +100,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): msg"Cannot use non-term member ${bms.nme} in term position" -> t.toLoc :: Nil getFlowSymOrType(bms) case _: Symbol => - log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") + log(dbg"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") P.Unknown(t) case Blk(stats, res) => @@ -142,20 +142,20 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): ) case N => P.Unknown(cd) - log(s"Class member type: ${prod.showDbg}") + log(dbg"Class member type: ${prod.showDbg}") constrain(prod, C.Flow(cd.bsym.flow)) case md: ModuleOrObjectDef => // TODO - log(s"Module: ${md.path}") + log(dbg"Module: ${md.path}") typeBody(md.body) case _: Import => // TODO? case stt => - log(s"/!\\ Unhandled statement: ${stt} /!\\") + log(dbg"/!\\ Unhandled statement: ${stt} /!\\") P.Unknown(stt) typeProd(res) @@ -165,21 +165,21 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case sel @ LeadingDotSel(nme) => leadingDotSelsToExpand += sel - log(s"Leading dot selection ${sel.showDbg}") + log(dbg"Leading dot selection ${sel.showDbg}") val sym = sel.resSym constrain(P.LeadingDotSel(sel), C.Flow(sym)) P.Flow(sym) case sel @ AnySel(pre, nme, cls) => - log(s"Selection ${sel.showDbg} ${sel.typ}") + log(dbg"Selection ${sel.showDbg} ${sel.typ}") checkLDS(pre): pre_t => sel.legacyResolvedSym match case S(sym: BlockMemberSymbol) => - log(s"RES ${sym.nme} in ${sel.showDbg}") + log(dbg"RES ${sym.nme} in ${sel.showDbg}") getFlowSymOrType(sym) case S(sym) => selsToExpand += sel - log(s"Unhandled symbol reference ${sym.nme} in ${sel.showDbg}") + log(dbg"Unhandled symbol reference ${sym.nme} in ${sel.showDbg}") P.Unknown(sel) case N => selsToExpand += sel @@ -192,7 +192,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case N => cls.legacyResolvedSym.flatMap(_.asCls) match case N => - log(s"Unresolved or invalid class symbol in ${cls.showDbg}") + log(dbg"Unresolved or invalid class symbol in ${cls.showDbg}") P.Unknown(nw) case S(sym) => sym match @@ -239,7 +239,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): */ def typeParam(p: Param): C = - trace[C](s"Typing param: ${p.showDbg}", post = res => s": ${res.showDbg}"): + trace[C](dbg"Typing param: ${p.showDbg}", post = res => s": ${res.showDbg}"): p.signType match case S(typ) => p.flow.producers += ConcreteProd(Vector.empty, P.Typ(typ)) @@ -253,7 +253,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): ps.params.map(typeParam) def typeType(t: Term): Type = - trace[Type](s"Typing consumer: ${t.showDbg}", post = res => s": ${res.showDbg}"): + trace[Type](dbg"Typing consumer: ${t.showDbg}", post = res => s": ${res.showDbg}"): t match case Capture(base, _) => typeType(base) case Ref(sym: VarSymbol) => Type.Ref(sym, Nil) // unparameterized type variable @@ -277,7 +277,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def expandTerms() = import SelectionTarget.* selsToExpand.foreach: sel => - log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") + log(dbg"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.map(_.showAsPlain).mkString(", ")}") assert(sel.expansion.isEmpty) sel.resolvedTargets match case ObjectMember(sym) :: Nil => @@ -286,7 +286,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case CompanionMember(comp, sym) :: Nil => val base = Sel(comp, Tree.Ident(sym.nme))(S(sym), FlowSymbol.sel(sym.nme), N, N) val app = App(base, Tup(sel.prefix :: Nil)(Tree.DummyTup))(Tree.DummyApp, N, FlowSymbol.app()) - log(s"Expansion: ${app.showDbg}") + log(dbg"Expansion: ${app.showDbg}") sel.expansion = S(S(app)) case Nil => // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) @@ -301,12 +301,12 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc leadingDotSelsToExpand.foreach: sel => - log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") + log(dbg"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.map(_.showAsPlain).mkString(", ")}") assert(sel.expansion.isEmpty) sel.resolvedTargets match case CompanionMember(comp, sym) :: Nil => val base = Sel(comp, Tree.Ident(sym.nme))(S(sym), FlowSymbol.sel(sym.nme), N, N) - log(s"Leading dot expansion: ${base.showDbg}") + log(dbg"Leading dot expansion: ${base.showDbg}") sel.expansion = S(S(base)) case Nil => // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) @@ -365,7 +365,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val (trm, cc) = collectedConstraints.pop() toSolve.push(cc) - trace(s"Handling constraint: ${cc.showDbg} (from ${trm.showDbg})"): + trace(dbg"Handling constraint: ${cc.showDbg} (from ${trm.showDbg})"): while fuel > 0 && toSolve.nonEmpty do @@ -374,27 +374,27 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def dig(lhs: P, rhs: C, path: Path): Unit = - log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName})") + log(dbg"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName})") (lhs, rhs) match case (P.Flow(sym), rhs) if inCache.contains(sym -> rhs) - => log(s"In (in) cache!") + => log(dbg"In (in) cache!") case (lhs, C.Flow(sym)) if outCache.contains(lhs -> sym) - => log(s"In (out) cache!") + => log(dbg"In (out) cache!") case (P.Flow(sym), C.Flow(sym2)) => - log(s"New flow $sym ~> $sym2") + log(dbg"New flow $sym ~> $sym2") sym.outFlows += sym2 sym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) case (lhs: ProdCtor, C.Flow(sym)) => - log(s"New flow $lhs ~> $sym") + log(dbg"New flow $lhs ~> $sym") sym.producers += ConcreteProd(path, lhs) sym.consumers.foreach: c => dig(lhs, c, path) case (P.Flow(sym), rhs) => - log(s"New flow $sym ~> $rhs") + log(dbg"New flow $sym ~> $rhs") sym.consumers += rhs sym.producers.foreach: cp => dig(cp.ctor, rhs, cp.path ++ path) @@ -431,14 +431,14 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): zip(args, ini, rst, path) case (sel @ P.LeadingDotSel(trm), rhs) => rhs match case C.Typ(Type.Ref(sym, _)) => - log(s"Examining ${sym} for leading dot selection resolution") + log(dbg"Examining ${sym} for leading dot selection resolution") getCompanionMember(trm.nme.name, trm.originalCtx, sym) match case S((path, memb)) => sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) - log(s"Found member ${memb}") + log(dbg"Found member ${memb}") toSolve.push(Constraint(getFlowSymOrType(memb), C.Flow(trm.resSym))) case _ => - log(s"Could not find member ${trm.nme.name} in ${sym}") + log(dbg"Could not find member ${trm.nme.name} in ${sym}") case _ => log("Unhandled RHS for leading dot selections") case (lhs, sel: C.Sel) => lhs match @@ -450,7 +450,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): d.body.members.get(sel.nme.name) match case S(memb: BlockMemberSymbol) => sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) - log(s"Found immediate member ${memb}") + log(dbg"Found immediate member ${memb}") toSolve.push(Constraint(getFlowSymOrType(memb), sel.res)) case N => d.moduleCompanion match @@ -458,11 +458,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val cd = comp.defn.getOrElse(die) cd.body.members.get(sel.nme.name) match case S(memb) => - log(s"Found companion member ${memb}") + log(dbg"Found companion member ${memb}") sel.trm.originalCtx match case S(oc) => val patho = findAccessPath(oc, cd.path, comp) - log(s"Access path: ${patho}") + log(dbg"Access path: ${patho}") patho match case S(path) => sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) @@ -488,7 +488,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): :: msg"Type `${lhs.showDbg}` does not contain member '${sel.nme.name}'" -> lhs.toLoc :: Nil) case _ => - log(s"/!\\ Unhandled constraint /!\\") + log(dbg"/!\\ Unhandled constraint /!\\") end dig dig(c.lhs, c.rhs, Vector.empty) @@ -498,7 +498,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): msg"Could not solve all constraints within $MAX_FUEL iterations." -> N :: Nil)) def findAccessPath(src: SrcScope, dst: SrcScope, moduleSym: ModuleOrObjectSymbol): Opt[Term] = - log(s"outermostAcessibleBase ${dst.outermostAcessibleBase}") + log(dbg"outermostAcessibleBase ${dst.outermostAcessibleBase}") val (outermostBase, outermostPath) = dst.outermostAcessibleBase var cur = src while cur isnt outermostBase do diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index feecca37bc..2d472115b5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -132,7 +132,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C * `freeSplitSyms` of the recursive result. */ private def normalizeImpl(split: Split)(using vs: VarSet): Split = split match case Split.Cons(Branch(scrutinee, pattern, consequent), alternative) => - log(s"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") + log(dbg"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") val specializedConsequent = consequent.specialized(+, scrutinee, pattern) if specializedConsequent.isFull then // The positive specialization of the consequent ends with `Else` or @@ -157,13 +157,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C case S(threshold) => normalizedAlternative.size * 2 > threshold case N => false if shouldShare then - log(s"SHARE: let-split ${splitSymbol.nme}, body size ${normalizedAlternative.size}") + log(dbg"SHARE: let-split ${splitSymbol.nme}, body size ${normalizedAlternative.size}") Split.LetSplit(splitSymbol, Branch(scrutinee, pattern, whenTrue) ~: useSplit) else - log(s"INLINE: body size ${normalizedAlternative.size} below threshold") + log(dbg"INLINE: body size ${normalizedAlternative.size} below threshold") Branch(scrutinee, pattern, inlineUseSplit(whenTrue, splitSymbol, normalizedAlternative)) ~: normalizedAlternative else - log(s"ABSORB: positive consequent absorbed $$${splitSymbol.nme}, no sharing") + log(dbg"ABSORB: positive consequent absorbed $$${splitSymbol.nme}, no sharing") Branch(scrutinee, pattern, whenTrue) ~: normalizedAlternative case (S(positiveAlternative), S(negativeAlternative)) => log("DUP: pos≠, neg≠") @@ -175,13 +175,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C log("DUP: pos=, neg≠") Branch(scrutinee, pattern, normalize(specializedConsequent ++ alternative.duplicate)) ~: normalize(negativeAlternative) case Split.Let(v, _, tail) if vs has v => - log(s"LET: SKIP already declared scrutinee $v") + log(dbg"LET: SKIP already declared scrutinee $v") normalizeImpl(tail) case Split.Let(v, rhs, tail) => - log(s"LET: $v") + log(dbg"LET: $v") Split.Let(v, rhs, normalizeImpl(tail)(using vs + v)) case split @ Split.Else(default) => - log(s"DFLT: ${default.showDbg}") + log(dbg"DFLT: ${default.showDbg}") split case Split.End => Split.End case Split.LetSplit(sym, tail) => Split.LetSplit(sym, normalizeImpl(tail)) @@ -227,23 +227,23 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C case Split.End => log("CASE Nil"); N case Split.Else(_) => log("CASE Else"); N case split @ Split.Let(sym, _, tail) => - log(s"CASE Let ${sym}") + log(dbg"CASE Let ${sym}") rec(tail).map(newTail => split.copy(tail = newTail)) case split @ Split.Cons(head @ Branch(thatScrutinee, thatPattern, continuation), tail) => - log(s"CASE Cons ${head.showDbg}") + log(dbg"CASE Cons ${head.showDbg}") if scrutinee.sym is thatScrutinee.sym then mode match case + => - log(s"Case 1.1: $scrutinee === $thatScrutinee") + log(dbg"Case 1.1: $scrutinee === $thatScrutinee") if thatPattern =:= pattern then - log(s"Case 1.1.1: $pattern =:= $thatPattern") + log(dbg"Case 1.1.1: $pattern =:= $thatPattern") S(aliasBindings(pattern, thatPattern)(rec(continuation).getOrElse(continuation) ++ rec(tail).getOrElse(tail))) else if thatPattern <:< pattern then - log(s"Case 1.1.2: $pattern <:< $thatPattern") + log(dbg"Case 1.1.2: $pattern <:< $thatPattern") pattern.markAsRefined rec(tail).map(newTail => split.copy(tail = newTail)) else thatPattern match case thatPattern: FlatPattern.Record => - log(s"Case 1.1.4: $thatPattern is a record") + log(dbg"Case 1.1.4: $thatPattern is a record") // we can use information if pattern is itself a record, or if it is a constructor with arguments val simplifiedRecord = thatPattern assuming pattern if simplifiedRecord.entries.isEmpty then @@ -264,40 +264,40 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C // case Pattern.Class(cls, _, _) => cls.toLoc // case _ => thatPattern.toLoc // })) - log(s"case 1.1.5: $pattern <:< $thatPattern") + log(dbg"case 1.1.5: $pattern <:< $thatPattern") N else if areProvablyDisjoint(pattern, thatPattern) then - log(s"Case 1.1.6: $pattern and $thatPattern are provably disjoint") + log(dbg"Case 1.1.6: $pattern and $thatPattern are provably disjoint") S(rec(tail).getOrElse(tail)) else // When patterns are not provably disjoint, we cannot assume // the scrutinee can't match both (e.g., conjunction patterns // like `A & B`). Keep the branch. - log(s"Case 1.1.6: $pattern and $thatPattern are not provably disjoint") + log(dbg"Case 1.1.6: $pattern and $thatPattern are not provably disjoint") (rec(continuation), rec(tail)) match case (N, N) => N case (optCont, optTail) => S(head.copy(continuation = optCont.getOrElse(continuation)) ~: optTail.getOrElse(tail)) case - => - log(s"Case 1.2: $scrutinee === $thatScrutinee") + log(dbg"Case 1.2: $scrutinee === $thatScrutinee") if thatPattern =:= pattern || thatPattern <:< pattern then - log(s"Case 1.2.1: $pattern =:= (or <:<) $thatPattern") + log(dbg"Case 1.2.1: $pattern =:= (or <:<) $thatPattern") S(rec(tail).getOrElse(tail)) else - log(s"Case 1.2.2: $pattern are unrelated to $thatPattern") + log(dbg"Case 1.2.2: $pattern are unrelated to $thatPattern") rec(tail).map(newTail => split.copy(tail = newTail)) else - log(s"Case 2: $scrutinee =/= $thatScrutinee") + log(dbg"Case 2: $scrutinee =/= $thatScrutinee") (rec(continuation), rec(tail)) match case (N, N) => N case (optCont, optTail) => S(head.copy(continuation = optCont.getOrElse(continuation)) ~: optTail.getOrElse(tail)) case split @ Split.LetSplit(sym, tail) => - log(s"CASE LetSplit ${sym.nme}") + log(dbg"CASE LetSplit ${sym.nme}") rec(tail).map(newTail => split.copy(tail = newTail)) case split @ Split.UseSplit(sym) => - log(s"CASE UseSplit ${sym.nme}") + log(dbg"CASE UseSplit ${sym.nme}") // UseSplit references a shared body. If the body mentions the current // scrutinee, inline it and specialize; otherwise keep the reference. // When rec returns N (body unchanged), the UseSplit is preserved to @@ -329,7 +329,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C Assign(sym, r, lowerSplit(tl, cont)) case Split.Cons(Branch(scrut, pat, tail), restSplit) => subTerm_nonTail(scrut): sr => - tl.log(s"Binding scrut $scrut to $sr (${summon[LoweringCtx].localVals})") + tl.log(dbg"Binding scrut $scrut to $sr (${summon[LoweringCtx].localVals})") def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, S(lowerSplit(restSplit, cont)), End() @@ -449,8 +449,8 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C def apply(t: Term.IfLike)(k: Result => Block)(using config: Config)(using LoweringCtx): Block = val newSplit = t.split.getExpandedSplit scoped("ucs:desugared"): - log(s"Split with nested patterns:\n${t.split.prettyPrint(t.kw)}") - log(s"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") + log(dbg"Split with nested patterns:\n${t.split.prettyPrint(t.kw)}") + log(dbg"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") this(newSplit, t.form, S(t), k) def apply(t: Term.SynthIf)(k: Result => Block)(using Config, LoweringCtx): Block = @@ -497,7 +497,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C val normalized = tl.scoped("ucs:normalize"): normalize(inputSplit)(using VarSet()) tl.scoped("ucs:normalized"): - tl.log(s"Normalized:\n${normalized.prettyPrint}") + tl.log(dbg"Normalized:\n${normalized.prettyPrint}") lazy val assignResult = (r: Result) => form match case IfLikeForm.ReturningIf => @@ -574,7 +574,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C else Begin(body, rest) scoped("ucs:lowered"): - log(s"Lowered:\n${block.showAsTree}") + log(dbg"Lowered:\n${block.showAsTree}") block end Normalization diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala index 710ee0b06a..b276459122 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -95,13 +95,13 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St extension (patterns: Ls[(Label, ExPat)]) /** Specialize a set of patterns. Also display them in the debug log. */ def specializePatterns(head: Opt[Head]): Ls[(Label, SpPat)] = - log(s"Specialized patterns of ${head.fold("the case without tags")(_.showDbg)}:") + log(dbg"Specialized patterns of ${head.fold("the case without tags")(_.showDbg)}:") patterns.map: (label, pattern) => val spec = pattern.specialize(head) val simp = spec.simplify - log(s"• Label $label") - log(s" ‣ Expanded: ${spec.showDbg}") - log(s" ‣ Simplified: ${simp.showDbg}") + log(dbg"• Label $label") + log(dbg" ‣ Expanded: ${spec.showDbg}") + log(dbg" ‣ Simplified: ${simp.showDbg}") (label, simp) private val labelMap: MutMap[Pat, Label] = MutMap() @@ -179,7 +179,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St // Make a split that tries all branches in order. val topmostSplit = branches.foldLeft(default: Split)((tail, branch) => branch ~: tail) val bodyTerm = SynthIf(topmostSplit) - log(s"Multi-matcher body:\n${topmostSplit.prettyPrint}") + log(dbg"Multi-matcher body:\n${topmostSplit.prettyPrint}") (paramList(param(scrutinee)), bodyTerm) def multiMatcherBranch( @@ -193,7 +193,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St .mkString("{", ", ", "}")}" ): val fields = patterns.iterator.flatMap((_, p) => p.fields).distinct.toList - log(s"fields: ${fields.iterator.map(_.showDbg).mkString("{", ", ", "}")}") + log(dbg"fields: ${fields.iterator.map(_.showDbg).mkString("{", ", ", "}")}") val subPatternsByField = fields.map: field => field -> patterns.iterator.flatMap((_, p) => p.collectSubPatterns(field)).toList val subScrutinees = Map.from(subPatternsByField.iterator.map: (field, subPatterns) => @@ -202,7 +202,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St val bindings = subPatternsByField.foldLeft(Ls.empty[Statement]): (statements, entry) => val (field, subPatterns) = entry val subScrutinee = subScrutinees(field) - log(s"subPattern for field ${field.showDbg}: ${ + log(dbg"subPattern for field ${field.showDbg}: ${ subPatterns.iterator.map(_.showDbg).mkString("{", ", ", "}")}") val subMatcherSymbol = buildMultiMatcher(subPatterns.iterator) /** Shorthands for the result of the matcher branch. */ @@ -311,7 +311,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St val makeMakeSplit = fields.iterator.foldRight( (fields: Ls[(Ident, Term)], bindingsSymbols: Ls[TempSymbol]) => ((makeConsequent, alternative) => - log(s"current bindings: " + aliases.map(_.name).mkString("{", ", ", "}")) + log(dbg"current bindings: " + aliases.map(_.name).mkString("{", ", ", "}")) val currentBindings = aliases.map: alias => RcdField(str(alias.name), scrutinee.safeRef) val bindings = bindingsSymbols match @@ -387,7 +387,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St val makeMakeSplit = fields.iterator.foldRight( (fields: Ls[RcdField], bindingsSymbols: Ls[TempSymbol]) => ((makeConsequent, alternative) => - log(s"current bindings: " + aliases.map(_.name).mkString("{", ", ", "}")) + log(dbg"current bindings: " + aliases.map(_.name).mkString("{", ", ", "}")) // The current pattern's bindings. val currentBindings = aliases.map: alias => RcdField(str(alias.name), scrutinee.safeRef) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala index eac76e5137..03ec87f1ec 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala @@ -560,10 +560,10 @@ class FixedPointCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, // The step pattern is not built from a recursive context. The fixed // point degenerates to a flat contraction loop: keep applying the // step pattern to its own output until it fails. - log(s"No recursive context; compiling a flat contraction loop.") + log(dbg"No recursive context; compiling a flat contraction loop.") S(assemble(entry, Nil, post, requireProgress)) case S((ctxInst, body)) => - log(s"Recursive context: ${ctxInst.showDbg}") + log(dbg"Recursive context: ${ctxInst.showDbg}") val alternatives = body match case Or(alternatives) => alternatives case pattern => pattern :: Nil @@ -800,7 +800,7 @@ class FixedPointCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, val (postMatcher, postImpls) = postCompiler.buildMatcher(postPattern, ResultMode.Full) (postMatcher, postImpls) - log(s"Classes: ${classes.map(cls => s"${cls.symbol.nme}(${cls.alts})").mkString(", ")}") + log(dbg"Classes: ${classes.map(cls => s"${cls.symbol.nme}(${cls.alts})").mkString(", ")}") // ---- Machine state ---- val inputSymbol = VarSymbol(Ident("input"), erasedType = N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala index e757cb8695..1c19d55848 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala @@ -49,7 +49,7 @@ class Instantiator(using codegen.Erasure)(using tl: TL)(using Ctx, State, Raise) val subst = defn.patternParams.iterator.map(_.sym).zip(instantiation.arguments).toMap log("Instantiating " + instantiation.showDbg) val instantiated = instantiate(defn.pattern)(using subst) - log(s"Instantiated ${instantiation.showDbg}\n" + + log(dbg"Instantiated ${instantiation.showDbg}\n" + s"arity = ${instantiation.symbol.defn.get.patternParams.size}\n" + s"arguments = ${instantiation.arguments.iterator.map(_.showDbg).mkString(", ")}\n" + s"instantiated = ${instantiated.showDbg}") @@ -68,7 +68,7 @@ class Instantiator(using codegen.Erasure)(using tl: TL)(using Ctx, State, Raise) progress += (instantiation -> N) worklist.enqueue(instantiation) else - log(s"Already instantiated ${instantiation.showDbg}") + log(dbg"Already instantiated ${instantiation.showDbg}") progress(instantiation) instantiation diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index 5fecd56947..5e97b53948 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -1339,7 +1339,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise alternative = failure ) pd.extractionParams.map(_.sym) - log(s"Translated `unapply`: ${topmost.prettyPrint}") + log(dbg"Translated `unapply`: ${topmost.prettyPrint}") makeMethod("unapply", pd.patternParams, inputSymbol, topmost) // TODO: Use `pd.extractionParams`. val unapplyStringPrefix = scoped("ucs:cp"): @@ -1350,7 +1350,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchSuccess(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") + log(dbg"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") makeMethod("unapplyStringPrefix", pd.patternParams, inputSymbol, topmost) unapply :: unapplyStringPrefix :: Nil @@ -1386,7 +1386,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val inputSymbol = VarSymbol(Ident("input"), erasedType = N) val topmost = makeMatchSplit(inputSymbol.toScrut, pattern, true) ((output, bindings) => Split.Else(makeMatchSuccess(output())), failure) - log(s"Translated `unapply`: ${topmost.prettyPrint}") + log(dbg"Translated `unapply`: ${topmost.prettyPrint}") makeUnapplyRecordStatements("unapply", patternParams, inputSymbol, topmost) val unapplyStringPrefix = scoped("ucs:cp"): // We don't report errors here because they have been already reported in @@ -1396,6 +1396,6 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchSuccess(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") + log(dbg"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) Term.Rcd(false, unapply ::: unapplyStringPrefix) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala index 75ee291ce9..4ceb69141a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala @@ -11,6 +11,8 @@ import Lexer._ import Tree.{IntLit, DecLit, StrLit} class Lexer(origin: Origin, dbg: Bool)(using raise: Raise): + + protected def doPrintDbg(msg: Str): Unit = println(msg) val bytes: Array[Char] = origin.fph.blockStr.toArray private val length = bytes.length @@ -368,8 +370,8 @@ class Lexer(origin: Origin, dbg: Bool)(using raise: Raise): val hasNewIndent = newIndBase.headOption.forall(_ < nextInd) && nextInd > 0 val newInd = if hasNewIndent then nextInd :: newIndBase else newIndBase if dbg then - println("dbg: " + bytes.drop(i).take(10).map(escapeChar).mkString+"...") - println((ind, nextInd, newIndBase, droppedNum, hasNewIndent, newInd)) + doPrintDbg("dbg: " + bytes.drop(i).take(10).map(escapeChar).mkString+"...") + doPrintDbg((ind, nextInd, newIndBase, droppedNum, hasNewIndent, newInd).toString) lex(k, newInd, if droppedNum > 0 then { if hasNewIndent then (INDENT, loc(j, k)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 321b338489..4833fdc2e9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -116,6 +116,11 @@ object Parser: def unapply(ts: Ls[TokLoc]): S[Ls[TokLoc]] = S(get(ts)) + private def isKey(id: IDENT, rest: Ls[TokLoc]): Bool = + !id.symbolic && rest.match + case NOISE(IDENT(":", _) -> _ :: _) => true + case _ => false + extension (loc: Loc) def showStart: String = loc.origin.fph.getLineColAt(loc.spanStart) match @@ -361,7 +366,7 @@ abstract class Parser( errExpr :: Nil case _ => blockOf(rule, a :: annotations, allowNewlines = allowNewlines) - case (tok @ (id: IDENT), loc) :: _ if id.name =/= ":" => + case (tok @ (id: IDENT), loc) :: rest if id.name =/= ":" && !isKey(id, rest) => Keyword.all.get(id.name) match case S(kw) => consume @@ -460,6 +465,8 @@ abstract class Parser( // case N => // err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found end of phrase instead" -> S(loc.left) :: Nil) // N + case (tok @ (id: IDENT), loc) :: rest if isKey(id, rest) => + tryParseExp(prec, tok, loc, rule, allowNewlines = allowNewlines) case (tok @ (id: IDENT), loc) :: _ => Keyword.all.get(id.name) match case S(Keyword.`:`) | N => @@ -646,6 +653,9 @@ abstract class Parser( consume val id = Tree.Ident(name).withLoc(S(loc)) exprCont(id, prec, allowNewlines = true) + case (tok @ IDENT(nme, _), loc) :: rest if isKey(tok, rest) => + consume + exprCont(Tree.Ident(nme).withLoc(S(loc)), prec, allowNewlines = allowNewlines) case (IDENT(nme, sym), loc) :: _ => Keyword.all.get(nme) match case S(kw) => // * Expressions starting with keywords should be handled in parseRule diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala index fa006ea449..74e66ca9cc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala @@ -52,7 +52,7 @@ class ReplHost(rootPath: Str)(using TL): case Some(syntaxErrorLine) => val message = syntaxErrorLine.substring(ReplHost.syntaxErrorHead.length) ReplHost.Error(true, message, reply.take(reply.indexOf(syntaxErrorLine)).trim()) - tl.log(s"REPL> Collected:\n${res}") + tl.log(dbg"REPL> Collected:\n${res}") res private def consumeStderr(): String = @@ -83,7 +83,7 @@ class ReplHost(rootPath: Str)(using TL): ReplHost.Error(false, reply.substring(begin + 1, end), reply.takeWhile(_ != 0x200b).trim()) else reply case error: ReplHost.Error => error - tl.log(s"REPL> Parsed:\n${parsed}") + tl.log(dbg"REPL> Parsed:\n${parsed}") parsed /** @@ -92,7 +92,7 @@ class ReplHost(rootPath: Str)(using TL): * @param code the code to execute */ private def send(code: Str): Unit = - tl.log(s"REPL> Sending: ${code}") + tl.log(dbg"REPL> Sending: ${code}") stdin.write(if code.endsWith("\n") then code else code + "\n") stdin.flush() diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 52d6da3a07..a29402f9c2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -52,15 +52,60 @@ extension (t: Any) ts.printPlain(t) -class DebugPrinter: +/** Interpolate compiler values through the selected debug printer, including symbols nested in + * collections and products. Ordinary string interpolation cannot carry a printing policy. */ +extension (context: StringContext) + def dbg(values: Any*)(using printer: DebugPrinter): Str = + context.s(values.map(printer.printPlain)*) + + +class DebugPrinter( + private val uidOwner: Opt[semantics.Elaborator.State] = N, + final val showUids: Bool = false, +): + require(!showUids || uidOwner.isDefined) + + protected def symbolName(sym: semantics.Symbol): Str = sym.prefix + sym.nme + protected def preProcess(t: Product): Product = t + protected def postProcess(t: Product): Str = "" + given DebugPrinter = this - def preProcess(t: Product): Product = t - def postProcess(t: Product): Str = "" + /** Capture UID visibility without replacing the symbol-naming or tree-processing hooks. + * A view points directly at the original printer, even after repeated visibility changes. */ + final def withUids(enabled: Bool): DebugPrinter = + if enabled === showUids then this + else this match + case view: DebugPrinter.UidView => view.underlying + case _ => new DebugPrinter.UidView(this, enabled) - def printPlain(v: Any): Str = v.toString + final def printPlain(v: Any): Str = v match + case null => "null" + case sym: semantics.Symbol => + // Numeric UIDs are unique only within their allocating unit. Foreign symbols must not + // acquire a local-looking identity just because the importing unit enables UID printing. + val showUid = showUids && uidOwner.exists(_ is sym.getState) + symbolName(sym) + (if showUid then s"‹${sym.uid}›" else "") + // Shapes contain inference graphs. Their explicit summaries avoid traversing those graphs. + case shape: semantics.Shape => shape.shwDbg + case marks: semantics.Marks => marks.showDbg + case flags: TermDefFlags => flags.toString + case flags: FldFlags => flags.toString + case flags: ParamListFlags => flags.toString + case path: io.Path => path.toString + case path: io.RelPath => path.toString + case xs: List[?] => xs.iterator.map(printPlain).mkString("List(", ", ", ")") + case xs: Vector[?] => xs.iterator.map(printPlain).mkString("Vector(", ", ", ")") + case xs: Array[?] => xs.iterator.map(printPlain).mkString("Array(", ", ", ")") + case xs: collection.Map[?, ?] => + xs.iterator.map((k, v) => s"${printPlain(k)} -> ${printPlain(v)}").mkString("Map(", ", ", ")") + case xs: collection.Set[?] => xs.iterator.map(printPlain).mkString("Set(", ", ", ")") + case tuple: Tuple => tuple.productIterator.map(printPlain).mkString("[", ", ", "]") + case p: Product if p.productArity > 0 => + p.productIterator.map(printPlain).mkString(p.productPrefix + "(", ",", ")") + case _ => v.toString - def printProduct(inTailPos: Bool, t: Product): Str = + final def printProduct(inTailPos: Bool, t: Product): Str = def aux(v: Any, inTailPos: Bool = false): Str = v match @@ -127,6 +172,12 @@ end DebugPrinter object DebugPrinter: + private final class UidView(val underlying: DebugPrinter, enabled: Bool) + extends DebugPrinter(underlying.uidOwner, enabled): + override protected def symbolName(sym: semantics.Symbol): Str = underlying.symbolName(sym) + override protected def preProcess(t: Product): Product = underlying.preProcess(t) + override protected def postProcess(t: Product): Str = underlying.postProcess(t) + val emptyValues: Set[Any] = Set( None, Nil, Vector.empty, semantics.PlainParamList(Nil)(N), diff --git a/hkmc2/shared/src/main/scala/utils/TraceLogger.scala b/hkmc2/shared/src/main/scala/utils/TraceLogger.scala index 225573c568..45ae7d0718 100644 --- a/hkmc2/shared/src/main/scala/utils/TraceLogger.scala +++ b/hkmc2/shared/src/main/scala/utils/TraceLogger.scala @@ -8,40 +8,74 @@ type TL = TraceLogger def tl(using TL): TL = summon -abstract class TraceLogger(using val debugPrinter: DebugPrinter): - def doTrace: Bool = true +abstract class TraceLogger( + final val debug: DebugContext, + final val sink: DebugSink, + indentWhenDisabled: Bool, + prefix: Str, +)(using printer: DebugPrinter): + protected def phaseEnabled(config: Config.Debug): Bool + protected def forceTrace(scope: Opt[Str]): Bool + + final def debugPrinter: DebugPrinter = printer.withUids(debug.showUids) + + final def isTracing: Bool = phaseEnabled(debug.config) || forceTrace(scope) + + /** Commands can force a trace without enabling the phase in source settings. + * Only source settings that enable the phase select a destination other than StdIO. */ + private def debugOutput: Config.DebugOutput = + if phaseEnabled(debug.config) then debug.config.out else Config.DebugOutput.StdIO - protected val noPostTrace: Any => Str = _ => "" + private val noPostTrace: Any => DebugPrinter ?=> Str = _ => "" - protected var indent = 0 - def trace[T](pre: => Str, post: T => Str = noPostTrace)(thunk: => T): T = + private var indent = 0 + final def trace[T](pre: DebugPrinter ?=> Str, post: T => DebugPrinter ?=> Str = noPostTrace)(thunk: => T): T = log(pre) - enter() - val res = try thunk finally exit() + val indentTrace = indentWhenDisabled || isTracing + if indentTrace then enter() + val res = try thunk finally if indentTrace then exit() if post isnt noPostTrace then log(post(res)) res - inline def traceNot[T](pre: => Str, post: T => Str = noPostTrace)(thunk: => T): T = + inline def traceNot[T](pre: DebugPrinter ?=> Str, post: T => DebugPrinter ?=> Str = noPostTrace)(thunk: => T): T = thunk inline def enter() = indent += 1 inline def exit() = indent -= 1 - protected[hkmc2] def emitDbg(str: Str): Unit = scala.Predef.println(str) + final def logs(msgs: (DebugPrinter ?=> Any)*): Unit = + if isTracing then msgs.foreach(msg => log(msg)) - inline def log(msg: => Any): Unit = log(msg, noIndent = false) + final def log(msg: DebugPrinter ?=> Any, noIndent: Bool = false): Unit = + if isTracing then + val printer = debugPrinter + val rendered = printer.printPlain(msg(using printer)) + sink.emit(debugOutput, prefix + ( + if noIndent then rendered + else "| " * indent + rendered.indentNewLines("| " * indent + "> ") + )) - def logs(msgs: => Any*): Unit = - if doTrace then msgs.foreach(log(_)) - - def log(msg: => Any, noIndent: Bool = false): Unit = - if doTrace then emitDbg(if noIndent then msg.toString - else "| " * indent + msg.toString.indentNewLines("| " * indent + "> ")) + final def traceDefinition[T](annotations: Ls[semantics.Annot], base: Config)(body: => T): T = + traceDefinitionThen[T, T](annotations, base)(k => k(body))(identity) + + final def traceDefinitionThen[R, T](annotations: Ls[semantics.Annot], base: Config) + (process: (R => T) => T)(k: R => T): T = + DebugOutputHandler.definitionConfig(annotations, base.copy(debug = debug.config)) match + case S(localConfig) => + debug.scopedThen(localConfig.debug, phaseEnabled(localConfig.debug))(process)(k) + case N => process(k) - protected var scope: Opt[Str] = N + private var scope: Opt[Str] = N - def scoped[T](flag: Str)(thunk: => T): T = - var oldScope = scope + final def scoped[T](flag: Str)(thunk: => T): T = + val oldScope = scope scope = S(flag) try thunk finally scope = oldScope +object TraceLogger: + def silent(using printer: DebugPrinter): TraceLogger = + val debug = new DebugContext: + override protected def forceUids: Bool = printer.showUids + new TraceLogger(debug, DebugSink.discard, indentWhenDisabled = true, prefix = ""): + protected def phaseEnabled(config: Config.Debug): Bool = false + protected def forceTrace(scope: Opt[Str]): Bool = false diff --git a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls index 196190f34a..a262fa1319 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls @@ -85,3 +85,37 @@ trait F //│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +// Value definitions do not take parameter lists. +:e +val badValue(arg) = arg +//│ ╔══[COMPILATION ERROR] Value definitions cannot have parameter lists +//│ ║ l.90: val badValue(arg) = arg +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Name not found: arg +//│ ║ l.90: val badValue(arg) = arg +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ badValue = undefined + +:e +fun localBadValue() = + val badLocal(arg) = arg + badLocal +//│ ╔══[COMPILATION ERROR] Value definitions cannot have parameter lists +//│ ║ l.102: val badLocal(arg) = arg +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Name not found: arg +//│ ║ l.102: val badLocal(arg) = arg +//│ ╙── ^^^ + +:e +class BadValueMember with + mut val badMember(arg)(arg2) = arg +//│ ╔══[COMPILATION ERROR] Mutable value definitions cannot have parameter lists +//│ ║ l.113: mut val badMember(arg)(arg2) = arg +//│ ╙── ^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Name not found: arg +//│ ║ l.113: mut val badMember(arg)(arg2) = arg +//│ ╙── ^^^ + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadDebugDirective.mls b/hkmc2/shared/src/test/mlscript/codegen/BadDebugDirective.mls new file mode 100644 index 0000000000..71b7549a13 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/BadDebugDirective.mls @@ -0,0 +1,59 @@ +:js + + +:e +#dbg('unknown) +//│ ╔══[COMPILATION ERROR] Unknown debugging aspect 'unknown' +//│ ║ l.5: #dbg('unknown) +//│ ╙── ^^^^^^^^ + + +:e +#dbg(elaboration: 1) +//│ ╔══[COMPILATION ERROR] Expected a boolean value +//│ ║ l.12: #dbg(elaboration: 1) +//│ ╙── ^ + + +:e +#dbg(out: File(1)) +//│ ╔══[COMPILATION ERROR] Expected StdIO or File("path") +//│ ║ l.19: #dbg(out: File(1)) +//│ ╙── ^^^^^^^ + + +// Parsing happens before directives can be read. +:e +#dbg('parsing) +//│ ╔══[COMPILATION ERROR] Unknown debugging aspect 'parsing' +//│ ║ l.27: #dbg('parsing) +//│ ╙── ^^^^^^^^ + + +// Unsupported local phases warn without suppressing supported ones. +:w +:expect 57 +@dbg('optimizations, 'lowering, showUids: false) fun mixed_local() = 57 +mixed_local() +//│ ╔══[WARNING] Debugging aspect 'optimizations' is not supported in @dbg; use #dbg(optimizations: true) instead. +//│ ║ l.36: @dbg('optimizations, 'lowering, showUids: false) fun mixed_local() = 57 +//│ ╙── ^^^^^^^^^^^^^^ +//│ Lowering.term 57 +//│ = 57 + + +// Annotations on statements other than definitions have no effect. +:w +@dbg('lowering) let not_a_definition = 1 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.47: @dbg('lowering) let not_a_definition = 1 +//│ ╙── ^^^^^^^^^^^^^^^ +//│ not_a_definition = 1 + +:w +fun local_let() = + @dbg('lowering) let not_a_definition = 2 + not_a_definition +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.55: @dbg('lowering) let not_a_definition = 2 +//│ ╙── ^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/DebugDestinations.mls b/hkmc2/shared/src/test/mlscript/codegen/DebugDestinations.mls new file mode 100644 index 0000000000..ecd6fffe3e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/DebugDestinations.mls @@ -0,0 +1,72 @@ +:js + + +:silent +val debugFile = "target/DebugDestinations.log" +fs.writeFileSync(debugFile, "old session contents") + + +@dbg('lowering, showUids: false, out: File("../../../../../../target/DebugDestinations.log")) +fun selected_file() = 42 +fun unrelated_file() = 43 + + +// Later definitions append to the same file, including methods. +module FileMethods with + @dbg('lowering, showUids: false, out: File("../../../../../../target/DebugDestinations.log")) + fun selected() = 45 + fun unrelated() = 46 + +:expect true +fs.readFileSync(debugFile, "utf8") == "Lowering.term 42\nLowering.term 45\n" +//│ = true + + +// A destination alone must not redirect tracing forced by diff-test commands. +#dbg(out: File("../../../../../../target/DebugDestinations.log")) + +:dl +@dbg(showUids: false) fun forced_output() = 47 +//│ Lowering.term 47 +//│ Lowering.term undefined +//│ avoidNames Scoped(Set(member:forced_output⁰),Define(FunDefn(None,member:forced_output⁰,term:forced_output¹,List... +//│ | avoidNames Define(FunDefn(None,member:forced_output⁰,term:forced_output¹,List(ParamList(‹›,List(),None)),Return... +//│ | | avoidNames Return(Lit(IntLit(47)))... +//│ | | avoidNames End()... + +// Analysis flags share the captured output sink without enabling optimization-phase traces +// or redirecting their output to the file selected above. +:flowBasedOpt debugDpe +:noInline +private fun forced_analysis(unused) = 48 +forced_analysis(0) +//│ dead-param-elim > >>> dead-param-elim results >>> +//│ dead-param-elim > forced_analysis⁰#0 @ -> eliminable: {0} +//│ dead-param-elim > <<< dead-param-elim results <<< +//│ = 48 + +#dbg(out: StdIO) + +:expect true +fs.readFileSync(debugFile, "utf8") == "Lowering.term 42\nLowering.term 45\n" +//│ = true + + +// Imported units share a handler. Relative destinations are resolved against the directory of +// the file that declares them, so relative and absolute destinations name the same log here. +// Generate fixtures under target/, outside test discovery, just like the emitted logs. +:silent +val importLog = "target/DebugImports.log" +val absoluteLog = JSON.stringify(fs.realpathSync("target") + "/DebugImports.log") +fs.writeFileSync(importLog, "old session contents") +fs.writeFileSync("target/DebugImportA.mls", "#lang(0.3.x)\n@dbg('lowering, showUids: false, out: File(\"DebugImports.log\"))\nmodule DebugImportA with\n fun value() = 2101\n") +fs.writeFileSync("target/DebugImportB.mls", "#lang(0.3.x)\nimport \"./DebugImportA.mls\"\n@dbg('lowering, showUids: false, out: File(" + absoluteLog + "))\nmodule DebugImportB with\n fun value() = 2102\n") + +:noCodeGen +import "../../../../../../target/DebugImportB.mls" + +:expect true +let trace = fs.readFileSync(importLog, "utf8") +trace.includes("Lowering.term 2101") && trace.includes("Lowering.term 2102") && not(trace.includes("old session contents")) +//│ = true +//│ trace = "Lowering.term 2101\nLowering.term { } (in stmt)\nLowering.term undefined (in stmt)\nLowering.term 2102\nLowering.term { } (in stmt)\nLowering.term undefined (in stmt)\n" diff --git a/hkmc2/shared/src/test/mlscript/codegen/DebugDirective.mls b/hkmc2/shared/src/test/mlscript/codegen/DebugDirective.mls new file mode 100644 index 0000000000..3943f9b339 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/DebugDirective.mls @@ -0,0 +1,141 @@ +:js + + +// Shorthand annotation debugging is scoped to the annotated definition. +@dbg('elaboration, showUids: false) fun debug_me(x) = x +fun do_not_debug_me(x) = x +//│ Processing term definition App(Ident(debug_me),Tup(List(Ident(x)))) +//│ Elaborating ParamTree: ParamTree(‹›,Ident(x),None,None,Set()) +//│ Elab term Ident(x) +//│ | Elab subterm Ident(x) +//│ | ~> x⁰ +//│ ~> x⁰ + + +// Named flags can be explicitly disabled. +#dbg(elaboration: false, out: StdIO) +fun named_debug_config(x) = x + + +// A top-level directive persists into the following block. +#dbg(elaboration: true, showUids: false) +//│ Elab block List(App(Directive(Ident(dbg),Tup(List())),Tup(List(InfixApp(Ident(elaboration),Keywrd(keyword ':'),[...] LocalScope(block:4) +//│ ~> { #config(...); } + +fun persistently_debugged(x) = x +//│ Elab block List(TermDef(Fun,App(Ident(persistently_debugged),Tup(List(Ident(x)))),Some(Ident(x)))) LocalScope(block:5) +//│ | Processing term definition App(Ident(persistently_debugged),Tup(List(Ident(x)))) +//│ | Elaborating ParamTree: ParamTree(‹›,Ident(x),None,None,Set()) +//│ | Elab term Ident(x) +//│ | | Elab subterm Ident(x) +//│ | | ~> x¹ +//│ | ~> x¹ +//│ ~> { fun member:persistently_debugged⁰(x¹) = x¹; } + +#dbg(elaboration: false) + + +module Main with + // The IR of `foo1` should be shown + @dbg('showParsedTree, 'showElaboratedTree, 'showElaboratedTerm, 'showLoweredTree, 'showOptimizedIR) + fun foo1 = 2 + 2 + // The IR of `foo2` should NOT be shown + fun foo2 = 2 + 2 +//│ ———————————————| Parsed tree |—————————————————————————————————————————————————————————————————————— +//│ Annotated: +//│ annotation = App: +//│ lhs = Ident of "dbg" +//│ rhs = Tup of Ls of +//│ Ident of "'showParsedTree" +//│ Ident of "'showElaboratedTree" +//│ Ident of "'showElaboratedTerm" +//│ Ident of "'showLoweredTree" +//│ Ident of "'showOptimizedIR" +//│ target = TermDef: +//│ k = Fun +//│ head = Ident of "foo1" +//│ rhs = S of OpApp: +//│ lhs = IntLit of 2 +//│ op = Ident of "+" +//│ rhss = Ls of +//│ IntLit of 2 +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ TermDefinition: +//│ k = Fun +//│ sym = member:foo1⁰ +//│ tsym = term:Main⁰/foo1¹ +//│ body = S of App: +//│ lhs = Ref{sym=builtin:+⁰} of builtin:+⁰ +//│ rhs = Tup of Ls of +//│ Fld: +//│ term = Lit of IntLit of 2 +//│ Fld: +//│ term = Lit of IntLit of 2 +//│ modulefulness = Modulefulness of N +//│ annotations = Ls of +//│ Debug of ... +//│ —————————————| Elaborated term |———————————————————————————————————————————————————————————————————— +//│ @dbg(...) fun foo1⁰::foo1¹ ‹flow:foo1⁰› = +(2, 2)‹app⁰› +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ FunDefn: +//│ owner = S of module:Main⁰ +//│ sym = member:foo1⁰ +//│ dSym = term:Main⁰/foo1¹ +//│ body = Return of Lit of IntLit of 4 +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ @dbg(...) define foo1⁰ as fun foo1¹ { return 4 } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +Main.foo1 +//│ = 4 + + +// The readable elaborated term distinguishes symbols that share a name. +@dbg('showElaboratedTerm) +fun shadowing(x) = + let x = x + 1 + x +//│ —————————————| Elaborated term |———————————————————————————————————————————————————————————————————— +//│ @dbg(...) fun shadowing⁰::shadowing¹(x⁰) ‹flow:shadowing⁰› = { let x¹, x¹ = +(x⁰, 1)‹app¹›, x¹ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +// Whole-program displays and optimization tracing use the same settings. +#dbg('showParsedTree, 'showElaboratedTree, 'showElaboratedTerm, 'showLoweredTree, 'showIR, 'showOptimizedIR, 'optimizations) +42 +//│ ———————————————| Parsed tree |—————————————————————————————————————————————————————————————————————— +//│ App: +//│ lhs = Directive: +//│ prefix = Ident of "dbg" +//│ body = Tup of Nil +//│ rhs = Tup of Ls of +//│ Ident of "'showParsedTree" +//│ Ident of "'showElaboratedTree" +//│ Ident of "'showElaboratedTerm" +//│ Ident of "'showLoweredTree" +//│ Ident of "'showIR" +//│ Ident of "'showOptimizedIR" +//│ Ident of "'optimizations" +//│ IntLit of 42 +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ SetConfig of ... +//│ res = Lit of IntLit of 42 +//│ —————————————| Elaborated term |———————————————————————————————————————————————————————————————————— +//│ #config(...), 42 +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Return of Lit of IntLit of 42 +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return 42 +//│ ⬤ Simplif. iter. 1 +//│ Captured variables: Set() +//│ ⬤ Simplif. iter. 1 +//│ Captured variables: Set() +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ return 42 +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 42 + +#dbg(showParsedTree: false, showElaboratedTree: false, showElaboratedTerm: false, showLoweredTree: false, showIR: false, showOptimizedIR: false, optimizations: false) diff --git a/hkmc2/shared/src/test/mlscript/codegen/DebugScoping.mls b/hkmc2/shared/src/test/mlscript/codegen/DebugScoping.mls new file mode 100644 index 0000000000..9bc54ecb7a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/DebugScoping.mls @@ -0,0 +1,144 @@ +:js + + +// A method must use the same definition-local lowering trace as a free function. +#dbg(showUids: false) +module DebugMethods with + @dbg('lowering) fun selected(x) = x + fun sibling(x) = x +//│ Lowering.term x⁰ – symbol x⁰ +//│ Expanding term Ref(x⁰) +//│ ~> Ref(x⁰) + +:expect 7 +DebugMethods.selected(7) +//│ = 7 + + +// Changing only UID display should retain the outer definition's trace flag. +@dbg('lowering, showUids: false) fun debug_outer() = + @dbg(showUids: false) fun debug_inner() = 42 + @dbg(lowering: false) fun debug_muted() = 43 + debug_inner() +//│ Lowering.term { fun member:debug_inner⁰() = 42; fun member:debug_muted⁰() = 43; member:debug_inner⁰() } +//│ Lowering.term 42 +//│ Lowering.term member:debug_inner⁰() +//│ Expanding term App(Ref(member:debug_inner⁰),Tup(List())) +//│ ~> App(Ref(member:debug_inner⁰),Tup(List())) +//│ Expanding term Ref(member:debug_inner⁰) +//│ ~> Resolved(Ref(member:debug_inner⁰),term:debug_inner¹) +//│ Lowering.term member:debug_inner⁰‹term:debug_inner¹› – symbol term:debug_inner¹ +//│ Expanding term Resolved(Ref(member:debug_inner⁰),term:debug_inner¹) +//│ ~> Resolved(Ref(member:debug_inner⁰),term:debug_inner¹) + + +// Diff-test debug commands trace the whole block. A definition's @dbg annotation that does not +// enable the corresponding phase must not mute them inside that definition. +:dl +@dbg(showUids: false) fun command_lowering() = 1601 +//│ Lowering.term 1601 +//│ Lowering.term undefined +//│ avoidNames Scoped(Set(member:command_lowering⁰),Define(FunDefn(None,member:command_lowering⁰,term:command_lower... +//│ | avoidNames Define(FunDefn(None,member:command_lowering⁰,term:command_lowering¹,List(ParamList(‹›,List(),None)),... +//│ | | avoidNames Return(Lit(IntLit(1601)))... +//│ | | avoidNames End()... + +:noCodeGen +:de +@dbg('lowering) fun command_elaboration(x) = x +//│ Elab block List(Annotated(App(Ident(dbg),Tup(List(Ident('lowering)))),TermDef(Fun,App(Ident(command_elaboration[...] LocalScope(block:6) +//│ | Processing term definition App(Ident(command_elaboration),Tup(List(Ident(x)))) +//│ | Elaborating ParamTree: ParamTree(‹›,Ident(x),None,None,Set()) +//│ | Elab term Ident(x) +//│ | | Elab subterm Ident(x) +//│ | | ~> x¹ +//│ | ~> x¹ +//│ ~> { fun member:command_elaboration⁰(x¹) = x¹; } + + +// A class's lowering trace includes its superclass reference, +// but not the statements that follow the class definition. +class DebugParent +@dbg('lowering, showUids: false) class DebugChild extends DebugParent +print(1701) +//│ Lowering.term member:DebugParent⁰ – symbol class:DebugParent¹ +//│ Expanding term Ref(member:DebugParent⁰) +//│ ~> Resolved(Ref(member:DebugParent⁰),class:DebugParent¹) +//│ Lowering.term { } (in stmt) +//│ Lowering.term undefined (in stmt) +//│ > 1701 + + +// An error in an annotated definition must not enable tracing for its following sibling. +:noCodeGen +:e +@dbg('elaboration, showUids: false) +fun debug_error() = missing_debug_name +fun after_debug_error(x) = x +//│ Processing term definition App(Ident(debug_error),Tup(List())) +//│ Elab term Ident(missing_debug_name) +//│ | Elab subterm Ident(missing_debug_name) +//│ ╔══[COMPILATION ERROR] Name not found: missing_debug_name +//│ ║ l.76: fun debug_error() = missing_debug_name +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ | ~> +//│ ~> + + +// Local values lower to assignments; their debug scope must end before the next value. +#lang(0.3.x) + +:expect 3 +fun debug_local_values() = + @dbg('lowering, showUids: false) val selected_local = 1 + val unrelated_local = 2 + selected_local + unrelated_local +debug_local_values() +//│ Lowering.term 1 +//│ = 3 + + +// Classes and their companions share lowering, while each elaboration annotation is scoped. +@dbg('elaboration, 'lowering, showUids: false) +class DebugCompanion(value: Int) with + fun answer() = 1101 +@dbg('elaboration, showUids: false) +module DebugCompanion with + fun answer() = 1102 +//│ Elaborating ParamTree: ParamTree(‹›,Ident(value),Some(Ident(Int)),None,Set()) +//│ Elab term Ident(Int) +//│ | Elab subterm Ident(Int) +//│ | | listen: trm = member:Int⁰ +//│ | ~> member:Int⁰^class:DebugCompanion⁰ +//│ ~> member:Int⁰^class:DebugCompanion⁰ +//│ transportType type of member:Int⁰ (previous = List(), marks = List(↘⟨class:DebugCompanion⁰⟩ϵ)) +//│ | .exit InstanceShape(missing) (↘⟨class:DebugCompanion⁰⟩ϵ) +//│ | | .exit InstanceShape(missing) (ϵ) +//│ | | = InstanceShape(missing) +//│ | = MarkedShape(InstanceShape(missing), ↘⟨class:DebugCompanion⁰⟩ϵ) +//│ Processing class definition Ident(DebugCompanion) +//│ | Companion: Some(module:DebugCompanion¹) +//│ | Elab block List(TermDef(Fun,App(Ident(answer),Tup(List())),Some(IntLit(1101)))) InnerScope(class:DebugCompanion⁰) +//│ | | Processing term definition App(Ident(answer),Tup(List())) +//│ | | Elab term IntLit(1101) +//│ | | | Elab subterm IntLit(1101) +//│ | | | ~> 1101 +//│ | | ~> 1101 +//│ | ~> { method fun member:answer⁰() = 1101; } +//│ Processing module/object definition Ident(DebugCompanion) +//│ | Companion: Some(class:DebugCompanion⁰) +//│ | Elab block List(TermDef(Fun,App(Ident(answer),Tup(List())),Some(IntLit(1102)))) InnerScope(module:DebugCompanion¹) +//│ | | Processing term definition App(Ident(answer),Tup(List())) +//│ | | Elab term IntLit(1102) +//│ | | | Elab subterm IntLit(1102) +//│ | | | ~> 1102 +//│ | | ~> 1102 +//│ | ~> { fun member:answer¹() = 1102; } +//│ Lowering.term 1101 +//│ Lowering.term { val member:value⁰ = value¹^term:DebugCompanion⁰/value²; } (in stmt) +//│ Lowering.term value¹^term:DebugCompanion⁰/value² – symbol value¹ +//│ Lowering.term value¹ – symbol value¹ +//│ Lowering.term undefined (in stmt) +//│ Lowering.term 1102 +//│ Lowering.term { } (in stmt) +//│ Lowering.term undefined (in stmt) diff --git a/hkmc2/shared/src/test/mlscript/codegen/DebugUids.mls b/hkmc2/shared/src/test/mlscript/codegen/DebugUids.mls new file mode 100644 index 0000000000..6a73265a2b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/DebugUids.mls @@ -0,0 +1,45 @@ +:js + + +// Imported symbols retain disambiguated names, but only the current unit's symbols get numeric UIDs. +:silent +fs.writeFileSync("target/DebugUidLibrary.mls", "#lang(0.3.x)\nmodule DebugUidLibrary with\n fun value() = 23\n") + +:noCodeGen +import "../../../../../../target/DebugUidLibrary.mls" + + +@dbg('lowering, showUids: true, out: File("../../../../../../target/DebugUids.log")) +fun with_uids(x) = DebugUidLibrary.value() + x +@dbg('lowering, showUids: false, out: File("../../../../../../target/DebugNoUids.log")) +fun without_uids(x) = DebugUidLibrary.value() + x + +:expect false +new RegExp("DebugUidLibrary[⁰-⁹]*‹[0-9]+›").test(fs.readFileSync("target/DebugUids.log", "utf8")) +//│ = false + +:expect true +:silent +let trace = fs.readFileSync("target/DebugUids.log", "utf8") +trace.includes("DebugUidLibrary") && new RegExp("x[⁰-⁹]*‹[0-9]+›").test(trace) + +:expect false +new RegExp("‹[0-9]+›").test(fs.readFileSync("target/DebugNoUids.log", "utf8")) +//│ = false + + +// A nested printer override must end before the next definition is lowered. +@dbg('lowering, showUids: false, out: File("../../../../../../target/DebugNestedUids.log")) +fun nested_uids(x) = + @dbg(showUids: true, out: File("../../../../../../target/DebugInnerUids.log")) + fun inner(y) = y + fun sibling(z) = z + inner(x) + sibling(x) + +:expect true +new RegExp("‹[0-9]+›").test(fs.readFileSync("target/DebugInnerUids.log", "utf8")) +//│ = true + +:expect false +new RegExp("‹[0-9]+›").test(fs.readFileSync("target/DebugNestedUids.log", "utf8")) +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLSyntax.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLSyntax.mls index ab47957bb5..756cbeae94 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLSyntax.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLSyntax.mls @@ -67,7 +67,7 @@ true and true false: Bool //│ Parsed: -//│ InfixApp(BoolLit(false),Keywrd(keyword ':'),Ident(Bool)) +//│ InfixApp(Ident(false),Keywrd(keyword ':'),Ident(Bool)) f of false //│ Parsed: diff --git a/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls b/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls index 8267ab6678..f6e54eef98 100644 --- a/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls +++ b/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls @@ -26,3 +26,32 @@ //│ = 333 +// UID-enabled traces retain the resolution and location hooks. +:loc +:dr +:ucs ups:tree +pattern IdentityWithUids = x => x +//│ elaborated pattern body: Transform loc[1:28-1:34]: +//│ > pattern = Alias loc[1:28-1:29]: +//│ > pattern = Wildcard loc[none] +//│ > id = Ident loc[1:28-1:29] of "x" +//│ > parameters = Ls of +//│ > Tuple2: +//│ > _1 = x⁰‹51› +//│ > _2 = x¹‹52› +//│ > transform = SimpleRef loc[1:33-1:34] of x¹‹52› + +// Resetting per-block commands also resets UID printing. +:loc +:ucs ups:tree +pattern IdentityWithoutUids = y => y +//│ elaborated pattern body: Transform loc[1:31-1:37]: +//│ > pattern = Alias loc[1:31-1:32]: +//│ > pattern = Wildcard loc[none] +//│ > id = Ident loc[1:31-1:32] of "y" +//│ > parameters = Ls of +//│ > Tuple2: +//│ > _1 = y⁰ +//│ > _2 = y¹ +//│ > transform = SimpleRef loc[1:36-1:37] of y¹ + diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 1e7f0e71b6..bb42812db7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -176,8 +176,8 @@ callid1(Predef) //│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | appShape isSaturated? true; head? member:id⁰, List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) -//│ | appShape: td.body = Some(SimpleRef(x)) +//│ | lhs false List([(x⁰), List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)]) +//│ | appShape: td.body = Some(SimpleRef(x⁰)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ //│ | .exit MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) @@ -197,8 +197,8 @@ callid1(Predef) //│ | | = IntroShape(1) //│ | = MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape isSaturated? true; head? member:callid1⁰, List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) -//│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id),None),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) +//│ | lhs false List([(p⁰), List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ)]) +//│ | appShape: td.body = Some(App(NewSel(SimpleRef(p⁰),Ident(id),None),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) //│ | listenTerm: trm = p⁰.id(1) //│ | listen: trm = p⁰.id(1) //│ | .exit MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) @@ -207,7 +207,7 @@ callid1(Predef) //│ | | .exit MarkedShape (MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ), term:callid1², callid1¹) //│ | | = IntroShape(1) //│ | = IntroShape(1) -//│ ~> { fun member:callid1(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } +//│ ~> { fun member:callid1⁰(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } //│ listenTerm: trm = member:callid1⁰(member:Predef⁰) //│ listen: trm = member:callid1⁰(member:Predef⁰) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls b/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls index 3d0ce0fad6..c95806fb56 100644 --- a/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls +++ b/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls @@ -27,7 +27,7 @@ in set y += 1 print(x + y) //│ ⬤ Simplif. iter. 1 -//│ Captured variables: HashSet() +//│ Captured variables: Set() //│ !! Change triggered { x⁰ ~> 2 } //│ !! Change triggered { y⁰ ~> 2 } //│ !! Change triggered { tmp:tmp⁰ ~> 3 } @@ -52,11 +52,11 @@ in //│ !! Change triggered { rm tmp:tmp¹ = 6 } //│ ▶ DCE: //│ > return Predef⁰.print⁰(6) -//│ Captured variables: HashSet() +//│ Captured variables: Set() //│ ⬤ Simplif. iter. 3 -//│ Captured variables: HashSet() +//│ Captured variables: Set() //│ ⬤ Simplif. iter. 1 -//│ Captured variables: HashSet() +//│ Captured variables: Set() //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ return Predef⁰.print⁰(6) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index e424c7aa11..69cb8603ef 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -51,7 +51,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,term:Handler$h$/f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h⁰ = member:Eff⁰() List(HandlerTermDefinition(r⁰,TermDefinition(Fun,member:f⁰,term:Handler$h$⁰/f¹,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r⁰),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None))) in member:foo⁰(h⁰) } :e ( @@ -75,7 +75,7 @@ handle h = Mod.Eff(3) with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = Sel(Ref(member:Mod),Ident(Eff))(Lit(IntLit(3))) List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,term:Handler$h$/f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,term:Handler$h$/g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h¹ = member:Mod⁰.Eff‹member:Eff¹›(3) List(HandlerTermDefinition(r¹,TermDefinition(Fun,member:f²,term:Handler$h$¹/f³,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r¹),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r²,TermDefinition(Fun,member:g⁰,term:Handler$h$¹/g¹,List(ParamList(‹›,List(Param(‹›,a⁰,None,Modulefulness(None))),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,None,Some(App(Ref(r²),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in member:foo⁰(h¹) } :e handle h = Eff with @@ -131,4 +131,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.129: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,term:Handler$h$/f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,term:Handler$h$/g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h² = member:Eff⁰() List(HandlerTermDefinition(r³,TermDefinition(Fun,member:f⁴,term:Handler$h$²/f⁵,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r³),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r⁴,TermDefinition(Fun,member:g²,term:Handler$h$²/g³,List(ParamList(‹›,List(Param(‹›,a¹,None,Modulefulness(None))),None)),None,None,Some(App(Ref(r⁴),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in member:foo⁰(h²) } diff --git a/hkmc2/shared/src/test/mlscript/parser/KeywordKeys.mls b/hkmc2/shared/src/test/mlscript/parser/KeywordKeys.mls new file mode 100644 index 0000000000..647106f66d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/parser/KeywordKeys.mls @@ -0,0 +1,43 @@ +:js + + +// A keyword followed by a colon is a key, like any other identifier. +:pt +(class: 1, true: 2, out : 3) +//│ ———————————————| Parsed tree |—————————————————————————————————————————————————————————————————————— +//│ Bra: +//│ k = Round +//│ inner = Block of Ls of +//│ InfixApp: +//│ lhs = Ident of "class" +//│ kw = Keywrd of keyword ':' +//│ rhs = IntLit of 1 +//│ InfixApp: +//│ lhs = Ident of "true" +//│ kw = Keywrd of keyword ':' +//│ rhs = IntLit of 2 +//│ InfixApp: +//│ lhs = Ident of "out" +//│ kw = Keywrd of keyword ':' +//│ rhs = IntLit of 3 +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = {class: 1, true: 2, out: 3} + +let r = (class: 1, true: 2, if: 3) +r.class + r.true + r.if +//│ = 6 +//│ r = {class: 1, true: 2, if: 3} + +fun f(x) = x +f(class: 1) +//│ = {class: 1} + +{fun: 4} +//│ = {fun: 4} + +// Keywords keep their meaning in other positions. +:expect true +true +//│ = true + +class C(val x: Int) diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls index c7cd8af007..678223a1be 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls @@ -54,11 +54,11 @@ if //│ Normalized: //│ > ‹if|while› //│ > let-split σ = y is []=1 and -//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(y⁰, 0) +//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple⁰›.)get‹term:Tuple⁰/get⁰›(y⁰, 0) //│ > let b = tmp:element0$⁰ //│ > else -//│ > let x -//│ > x = 1 +//│ > let x⁰ +//│ > x⁰ = 1 //│ > builtin:+⁰(x⁰, b⁰) //│ > x is true and //│ > z is true then 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ScrutineeIdentity.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ScrutineeIdentity.mls index 646ae77a3c..148824cf13 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ScrutineeIdentity.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ScrutineeIdentity.mls @@ -32,8 +32,8 @@ fun tuple(x) = if //│ Normalized: //│ > ‹if|while› //│ > x is []=2 and -//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(x⁰, 0) -//│ > let tmp:element1$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(x⁰, 1) +//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple⁰›.)get‹term:Tuple⁰/get⁰›(x⁰, 0) +//│ > let tmp:element1$ = ((tmp:runtime⁰.)Tuple‹module:Tuple⁰›.)get‹term:Tuple⁰/get⁰›(x⁰, 1) //│ > tmp:element0$ is 0 and //│ > let y = tmp:element1$⁰ //│ > tmp:element1$ is 1 then y⁰ diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls index 019303aeb4..e4f5798707 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls @@ -14,7 +14,7 @@ pattern Zero = 0 //│ > let tmp:scrut = 0 //│ > let tmp:patternArgument0$ = member:Zero⁰ //│ > let tmp:unapplyResult = (member:Nullable⁰.)unapply(tmp:patternArgument0$⁰, tmp:scrut⁰) -//│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess›(output, bindings) then true +//│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess⁰›(output, bindings) then true //│ > else false //│ = true @@ -26,7 +26,7 @@ fun foo(x) = x is Nullable(Char.Letter) //│ > ‹if|while› //│ > let tmp:patternArgument0$ = member:Char⁰^term:foo⁰.Letter //│ > let tmp:unapplyResult = (member:Nullable⁰^term:foo⁰.)unapply(tmp:patternArgument0$¹, x⁰) -//│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess›(output, bindings) then true +//│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess⁰›(output, bindings) then true //│ > else false foo of null diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index f2e5d08719..0f02d46e4a 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -45,18 +45,16 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val definitionMetadataNme = baseScp.allocateName(Elaborator.State.definitionMetadataSymbol)(using throw _) val prettyPrintNme = baseScp.allocateName(Elaborator.State.prettyPrintSymbol)(using throw _) - val ltl = new TraceLogger: - override def doTrace = debugLowering.isSet || scope.exists: - showUCS.get.getOrElse(Set.empty).contains - override def emitDbg(str: String): Unit = output(str) + val ltl = new DiffTraceLogger(indentWhenDisabled = true): + protected def phaseEnabled(config: Config.Debug): Bool = config.lowering + protected def forceTrace(scope: Opt[Str]): Bool = + debugLowering.isSet || scope.exists(showUCS.get.getOrElse(Set.empty).contains) - val dtl = new TraceLogger: - override def doTrace = debugOptimizations.isSet - override def emitDbg(str: String): Unit = output(str) + val dtl = new DiffTraceLogger(indentWhenDisabled = true): + protected def phaseEnabled(config: Config.Debug): Bool = config.optimizations + protected def forceTrace(scope: Opt[Str]): Bool = debugOptimizations.isSet - val replTL = new TraceLogger: - override def doTrace = showRepl.isSet - override def emitDbg(str: String): Unit = output(str) + val replTL = commandLogger(showRepl) lazy val host = hostCreated = true @@ -173,19 +171,17 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if showLoweredTree.isSet then outputSeparator("Lowered IR Tree") output(prog.showAsTree) + else DebugOutputHandler.showLoweredTree(prog)(outputDebugSection) if showIR.isSet || showIRLines.isSet then - given ShowCfg = ShowCfg( - showErasedTypes = showIRErasedTypes.isSet, - showExpansionMappings = false, - showFlowSymbols = true, - debug = debug.isSet, - ) - val irStr = Printer().worksheet(prog)(using irPrintingScp).mkString(output.ColWidth) + val irStr = print(prog) val sloc = irStr.count(_ == '\n') + 1 if showIRLines.isSet then output(s"Lines of IR: ${sloc}") if showIR.isSet then outputSeparator("Lowered IR") output(irStr) + else if config.debug.showIR then + outputDebugSection("Lowered IR", irStr, config.debug.out) + else DebugOutputHandler.showIR(prog, false, blockPrinter, output.ColWidth)(outputDebugSection)(using config, irPrintingScp) super.preOptimizeHook(prog) customPipeline.run(lowered, print, symbolsToPreserve, dtl) @@ -194,13 +190,8 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if showOptimizedIR.isSet then outputSeparator("Optimized IR") - given ShowCfg = ShowCfg( - showErasedTypes = showIRErasedTypes.isSet, - showExpansionMappings = false, - showFlowSymbols = true, - debug = debug.isSet, - ) - output(Printer().worksheet(optimized)(using irPrintingScp).mkString(output.ColWidth)) + output(print(optimized)) + else DebugOutputHandler.showIR(optimized, true, blockPrinter, output.ColWidth)(outputDebugSection)(using config, irPrintingScp) if showOptimizedTree.isSet then outputSeparator("Optimized IR Tree") output(optimized.showAsTree) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index bdd2ca7dcf..6d08512348 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -179,6 +179,7 @@ abstract class MLsDiffMaker extends DiffMaker: commentGeneratedCode = debug.isSet, noFreeze = noFreeze.isSet, noModuleCheck = noModuleCheck.isSet, + debug = Config.Debug.default, optimizer = Optimizer( deforest = Opt.when(deforest.isSet): val flags = parseFlags(deforest.get) @@ -234,12 +235,17 @@ abstract class MLsDiffMaker extends DiffMaker: ln.split(" ").iterator.map(x => "ucs:" + x.trim).toSet - given Elaborator.State = new Elaborator.State: - override def dbg: Bool = - dbgParsing.isSet - // || dbgElab.isSet - || dbgResolving.isSet - || debug.isSet + protected lazy val debugContext = new DebugContext: + override protected def forceUids: Bool = dbgParsing.isSet || dbgResolving.isSet || debug.isSet + protected lazy val debugOutputHandler = DebugOutputHandler(cctx.fs, str => output(str)) + + protected def outputDebugSection(title: Str, contents: Str, out: Config.DebugOutput): Unit = out match + case Config.DebugOutput.StdIO => + outputSeparator(title) + output(contents) + case _ => debugOutputHandler.emit(out, s"${title}\n${contents}") + + given Elaborator.State = new Elaborator.State protected lazy val dbgScp: utils.Scope = // for unique symbol debug-printing only @@ -251,42 +257,39 @@ abstract class MLsDiffMaker extends DiffMaker: val dbgPrinter: SymbolPrinter = new SymbolPrinter(dbgScp): - override def preProcess(t: Product): Product = super.preProcess: + override protected def preProcess(t: Product): Product = case class Unexpanded(origin: Resolvable) t match - case t: Resolvable if t.hasExpansion => t.expanded - case t: Resolvable if dbgResolving.isSet => Unexpanded(t.duplicate.resolve) - case t => t - override def postProcess(t: Product): Str = - super.postProcess(t) + ( - if showLocations.isSet then - t match + case t: Resolvable if t.hasExpansion => t.expanded + case t: Resolvable if dbgResolving.isSet => Unexpanded(t.duplicate.resolve) + case t => t + override protected def postProcess(t: Product): Str = + if showLocations.isSet then + t match case t: Located => t.toLoc.fold(" loc[none]"): loc => val (sl, _, sc) = loc.origin.fph.getLineColAt(loc.spanStart) val (el, _, ec) = loc.origin.fph.getLineColAt(loc.spanEnd) s" loc[$sl:$sc-$el:$ec]" - else "" - ) + case _ => "" + else "" - val etl = new TraceLogger: - override def doTrace = dbgElab.isSet || scope.exists: - showUCS.get.getOrElse(Set.empty).contains - override def emitDbg(str: String): Unit = output(str) - override def trace[T](pre: => Str, post: T => Str = noPostTrace)(thunk: => T): T = - // * This override is for avoiding to increase the indentation when tracing if doTrace is false, - // * so that selectively-enabled tracing doesn't get strange indentation. - // * Perhaps this should be the default behavior of TraceLogger. - if doTrace then super.trace(pre, post)(thunk) - else thunk + protected abstract class DiffTraceLogger(indentWhenDisabled: Bool) + extends TraceLogger(debugContext, debugOutputHandler, indentWhenDisabled, prefix = "")(using dbgPrinter) + + protected def commandLogger(command: Command[?]): TraceLogger = + new DiffTraceLogger(indentWhenDisabled = true): + protected def phaseEnabled(config: Config.Debug): Bool = false + protected def forceTrace(scope: Opt[Str]): Bool = command.isSet + + val etl = new DiffTraceLogger(indentWhenDisabled = false): + protected def phaseEnabled(config: Config.Debug): Bool = config.elaboration + protected def forceTrace(scope: Opt[Str]): Bool = + dbgElab.isSet || scope.exists(showUCS.get.getOrElse(Set.empty).contains) - val rtl = new TraceLogger: - override def doTrace = dbgResolving.isSet - override def emitDbg(str: String): Unit = output(str) + val rtl = commandLogger(dbgResolving) - val ftl = new TraceLogger: - override def doTrace = dbgFlow.isSet - override def emitDbg(str: String): Unit = output(str) + val ftl = commandLogger(dbgFlow) var curCtx = Elaborator.State.init var curICtx = Resolver.ICtx.empty @@ -409,34 +412,39 @@ abstract class MLsDiffMaker extends DiffMaker: case err: Throwable => uncaught(err) - given tl: TraceLogger with - override def doTrace = debug.isSet - override def emitDbg(str: String): Unit = output(str) + given tl: TraceLogger = commandLogger(debug) def processOrigin(origin: Origin)(using Raise): Unit = val oldCtx = curCtx - given Config = configModify(mkConfig) + val baseConfig = configModify(mkConfig) + given Config = baseConfig - val lexer = new syntax.Lexer(origin, dbg = dbgParsing.isSet) + val lexer = new syntax.Lexer(origin, dbg = dbgParsing.isSet): + override protected def doPrintDbg(msg: Str): Unit = output(msg) val tokens = lexer.bracketedTokens if showParse.isSet || dbgParsing.isSet then output(syntax.Lexer.printTokens(tokens)) - val rules = syntax.ParseRules() - val p = new syntax.Parser(origin, tokens, rules, raise, dbg = dbgParsing.isSet): + val parser = new syntax.Parser(origin, tokens, syntax.ParseRules(), raise, dbg = dbgParsing.isSet): def doPrintDbg(msg: => Str): Unit = if dbg then output(msg) - val res = p.parseAll(p.block(allowNewlines = true)) + val res = parser.parseAll(parser.block(allowNewlines = true)) + val phaseConfig = Config.elaborationConfig(res, file.up) + debugContext.configure(phaseConfig.debug) // If parsed tree is displayed, don't show the string serialization. - if (parseOnly.isSet || showParse.isSet) && !showParsedTree.isSet then + if (parseOnly.isSet || showParse.isSet) && !showParsedTree.isSet && !phaseConfig.debug.showParsedTree then output(s"Parsed:${res.map("\n\t"+_.showDbg).mkString}") if showParsedTree.isSet then outputSeparator(s"Parsed tree") res.foreach(t => output(t.showAsTree)) + else if phaseConfig.debug.showParsedTree then + outputDebugSection("Parsed tree", res.map(_.showAsTree).mkString("\n"), phaseConfig.debug.out) + else DebugOutputHandler.showParsedDefinitions(res, phaseConfig, file.up): (tree, out) => + outputDebugSection("Parsed tree", tree.showAsTree, out) // if showParse.isSet then // output(s"AST: $res") @@ -463,7 +471,8 @@ abstract class MLsDiffMaker extends DiffMaker: def processTrees(trees: Ls[syntax.Tree])(using Config, Raise): Unit = val blk = new syntax.Tree.Block(trees) - val elaborationConfig = Config.elaborationConfig(blk) + val elaborationConfig = Config.elaborationConfig(blk, file.up) + debugContext.configure(elaborationConfig.debug) // val blockSymbol = // semantics.TopLevelSymbol("block#"+blockNum) blockNum += 1 @@ -481,11 +490,13 @@ abstract class MLsDiffMaker extends DiffMaker: extractConfig(e.stats) // If elaborated tree is displayed, don't show the string serialization. - if (showElab.isSet || debug.isSet) && !showElaboratedTree.isSet then + if (showElab.isSet || debug.isSet) && !showElaboratedTree.isSet && !elaborationConfig.debug.showElaboratedTree then output(s"Elab: ${e.showDbg}") if showElaboratedTree.isSet then outputSeparator(s"Elaborated tree") output(e.showAsTree) + else DebugOutputHandler.showElaboratedTree(e, elaborationConfig)(outputDebugSection) + DebugOutputHandler.showElaboratedTerm(e, elaborationConfig, output.ColWidth)(outputDebugSection)(using flowScp) if showResl.isSet then import semantics.ShowCfg @@ -507,6 +518,7 @@ abstract class MLsDiffMaker extends DiffMaker: def processTerm(trm: semantics.Term.Blk, inImport: Bool)(using Config, Raise): Unit = given Ctx = curCtx given Config = Config.extractConfigFromStats(trm) + debugContext.configure(config.debug) if file.toString =/= runtimeSourceFile.toString && file.toString =/= preludeFile.toString then summon[Elaborator.State].initRuntimeSymbolsFromFile(runtimeSourceFile, prelude)( using summon[TL], summon[Raise], cctx)