Skip to content

Debug directives - #545

Merged
LPTK merged 38 commits into
hkust-taco:hkmc2from
LPTK:debug-directives
Oct 21, 2026
Merged

LPTK merged 38 commits into
hkust-taco:hkmc2from
LPTK:debug-directives

Conversation

@LPTK

@LPTK LPTK commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6 Astra summary below.
This PR also does a bunch of refactoring to clean up the debugging logic of the codebase.


Compiler traces and tree/IR displays can now be requested in source code through #dbg(...) directives and @dbg(...) definition annotations, in both the compiler and diff tests. Language reference, section 20 documents the syntax and behavior.

  • Tracing: elaboration, lowering, and optimizations. New name resolution happens during elaboration, so elaboration traces it; the legacy resolver is not traced by directives (:dr still traces it in diff tests).
  • Displays: showParsedTree, showElaboratedTree, showElaboratedTerm (readable form, as in :sr), showLoweredTree, showIR, and showOptimizedIR; showUids controls symbol UID display in traces.
  • Syntax: quoted flags such as #dbg('lowering) or named booleans such as #dbg(lowering: false).
  • Destination: out: StdIO or out: File("path"), with relative paths resolved against the declaring file's directory, as for imports. Each file is replaced on first use in a compilation session, then appended to, including across imported units.

Parser tracing remains available through the diff-test command :dp. Source directives are read after parsing, without a discovery/reparse pass or buffered diagnostic replay. Parsed-tree display remains available through showParsedTree.

@dbg scopes tracing to functions, values, methods, classes, and modules. Nested annotations inherit enclosing trace settings, and continuations processing sibling statements restore the enclosing scope. Class lowering includes its superclass evaluation and companion module. Definition-local optimizations settings warn because optimization passes process whole programs.

Diff-test commands force tracing independently of annotations. Forced-only traces go to the test output; when source settings also enable the phase, their destination applies. The compiler and diff tests share output routing, definition scoping, and tree/IR display helpers. File output uses FileSystem.append rather than repeatedly rewriting accumulated logs.

The PR also diagnoses parameter lists on value definitions instead of letting them fail during lowering. It also parses any alphanumeric keyword followed by a colon as a key (as in out: e, class: e, or true: e), so that record fields and named arguments can use keyword names.

Validation: ctest, dtest Debug, and hkmc2AllTests/test all pass. Four .mls debugging test files cover directives, diagnostics, displays, scoping, forced tracing, and file routing across imports. Generated fixtures and logs stay under target/, with no rmSync cleanup. A Scala check covers immutable in-memory filesystem snapshots after appends, which golden output cannot establish. Updated goldens are committed.

Known limitations:

  • Top-level directives configure the whole compilation unit regardless of their position; diff tests apply them block by block.
  • Imported units' StdIO traces go to process stdout rather than the diff-test output.
  • Nested annotations' tree/IR displays use their own or the file's destination; only traces inherit an enclosing annotation's destination.
  • Concurrent writes to one debug file are best effort and may interleave.

codex added 7 commits August 10, 2026 19:20
# Conflicts:
#	hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala
#	hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala
#	hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
#	hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala
Adapt debug directives to explicit annotation locations, erasure, and the new resolver. Preserve local-value debug scoping and refresh golden outputs.

Validation: ctest, focused debug diff tests, and hkmc2AllTests/test (1,081 tests passed).
@LPTK LPTK added the slopfest label Oct 16, 2026
LPTK and others added 3 commits October 16, 2026 13:16
Share directive discovery, parsing, definition traversal, and trace scoping between the compiler and diff tests. Report parsing diagnostics once, honor method lowering traces, and inherit nested debug settings. Avoid reindenting the elaborator body and remove unused parsing overrides.

Add diff tests for scoped and file output, plus explicit FIXME coverage for unsupported local parsing/optimization and class/module trace hooks. Include the latest golden output. Validation: ctest and hkmc2AllTests/test.
Warn when local debug annotations request parsing or optimization traces. Reuse definition scopes for class and module elaboration and lowering.

Append file output through the filesystem abstraction and share one handler across imported units. Keep concurrent debugging writes best effort.

Add diff regressions, a linear I/O check, and in-memory append coverage. Validate with ctest and hkmc2AllTests/test; include updated golden output.
@LPTK
LPTK marked this pull request as ready for review October 16, 2026 06:14
claude and others added 12 commits October 16, 2026 16:24
Reproduce, with their current wrong output:
- diff-test commands (`:dl`, `:de`, `:dr`) muted inside definitions
  whose `@dbg` annotation does not enable the same phase;
- lowering traces of a class with a superclass leaking into the
  statements that follow the class;
- `:dp` parse traces redirected to a `#dbg(out: File(...))` destination;
- absolute `File(...)` destinations crashing the compiler;
- value definitions with parameter lists crashing lowering.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A definition-local `@dbg` override replaced the trace logger's and the
elaboration state's enablement wholesale, so an annotation that did not
enable a phase (e.g. `@dbg(showUids: false)` under `:dl`) silently muted
the block-wide tracing requested by `:dl`, `:de`, `:dr`, `:d`, or UCS
scopes inside that definition.

Separate the debugging that diff-test commands force (`doForceTrace`,
`doForceDbg`) from the configurable part: overrides can still enable a
phase, and disable a phase enabled by `#dbg`, but forced debugging stays
on and keeps writing to standard output.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
For a class with an `extends` clause, the `@dbg` lowering scope wrapped
the continuation-passing evaluation of the superclass, whose continuation
also lowers every statement following the class, so those siblings were
traced with the class's settings.

Share the continuation-aware scoping that local values already used as
`DebugOutputHandler.traceDefinitionThen`, and use it for both: the
superclass and the class definition stay in the class's debug scope,
while the remaining statements are lowered in the enclosing one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`DebugOutputHandler.parse` sent every traced parse to the destination
selected by `#dbg(out: ...)`, even when only the `:dp` diff-test command
requested the trace, so a file destination swallowed `:dp` output.
Like the other forced traces, use the selected destination only when
`#dbg` itself enables parsing, and standard output otherwise.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`DebugOutputHandler.emit` always built `baseDir / io.RelPath(path)`, and
`io.RelPath` rejects absolute paths with an exception, so a destination
such as `#dbg(out: File("/tmp/trace.log"))` crashed the compiler on its
first trace line. Resolve destinations like import paths instead: keep
absolute paths as they are, and resolve others against the base
directory, through a shared `io.Path.resolve`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The elaborator accepted `val f(x) = ...`, producing a value definition
with parameters, which lowering cannot represent: top-level values hit
an assertion in lowering, and local values used to lose their parameters
silently and fail later in code generation. The debug-scoping refactor
of value lowering extended the assertion to local values as well.

Report a compilation error instead, and elaborate the value without its
parameter lists, so uses of the parameters in the body are reported too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add a "Compiler Debugging" section describing the `#dbg` directive and
the `@dbg` annotation: the available aspects, their syntax, output
destinations, scoping rules, and current limitations. Remove the
working notes in DBG_DIRECTIVE_PLAN.md, which this section supersedes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Document that explicit phase configuration selects the output destination even when a diff-test command also forces tracing. The implementation already behaves this way.

Reviewed the six follow-up fixes through d0425a5. Validation: ctest, focused debug and invalid-definition regressions, and hkmc2AllTests/test.
# Conflicts:
#	hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Keep parser tracing in the diff-test :dp command and remove source-directed discovery and replay. Share IR display selection and formatting between the compiler and diff tests.

Consolidate nine debug diff tests into four, retain scope and routing regressions, and write generated fixtures and logs under target without rmSync cleanup. Update the language reference and include regenerated goldens.

Validation: ctest, dtest Debug, and hkmc2AllTests/test all pass.
# Conflicts:
#	hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
Comment thread doc/reference.md
Comment thread doc/reference.md Outdated
Comment thread hkmc2/jvm/src/test/scala/hkmc2/DebugOutputHandlerTest.scala Outdated
claude and others added 5 commits October 18, 2026 00:52
- Add the `showElaboratedTerm` debugging aspect, which prints the
  elaborated term in readable form with the settings of the `:sr`
  diff-test command, as `showIR` does for the IR. It shares the whole
  block/annotated definition selection with `showElaboratedTree`.
- Remove the `resolution` aspect: it only traced the legacy resolver.
  New resolution happens during elaboration, so `elaboration` traces it.
  The `:dr` diff-test command still traces the legacy resolver.
- Remove DebugOutputHandlerTest, which did not pay for its maintenance.
- Restore the original indentation of local value lowering, a
  commented-out reference to `Assign`, and the original comment on the
  diff-test elaboration logger's `trace` override.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Record fields and named arguments such as `#dbg(out: ...)` may need
names that are keywords. Instead of a dedicated parse rule for `out:`,
parse any alphanumeric keyword followed by a colon as an identifier, in
statement, rule, and simple-expression positions. No keyword-introduced
syntax continues with a colon, and expressions are ascribed with `as`,
so this is unambiguous; `true: e` is a key too. Selections such as
`r.class` already worked, since the lexer makes them selection tokens.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`@dbg` on a `let` binding was silently accepted and had no effect.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Relative `File(...)` destinations were resolved against the compiler's
root base directory, so the same directive named different files
depending on how a unit was compiled; in diff tests, the test file and
the units it imports used different bases. Resolve them like imports,
against the directory of the file containing the directive, when the
directive is parsed: `DebugOutput.File` now holds the resolved path,
and `DebugOutputHandler` no longer needs a base directory.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@LPTK
LPTK merged commit 684e360 into hkust-taco:hkmc2 Oct 21, 2026
1 check passed
@LPTK
LPTK deleted the debug-directives branch October 21, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants