Skip to content

Formatting and debugging improvements#1115

Merged
keyboardDrummer merged 6 commits into
main2from
formatting-and-debugging-improvements
Jun 1, 2026
Merged

Formatting and debugging improvements#1115
keyboardDrummer merged 6 commits into
main2from
formatting-and-debugging-improvements

Conversation

@keyboardDrummer-bot

Copy link
Copy Markdown
Collaborator

Summary

Extracts the formatting and debugging improvements from #34 into a standalone PR.

Formatting improvements

  • Block formatting: Changes block output from single-line { stmt1; stmt2 } to vertical layout with indent(2):
    {
      stmt1;
      stmt2
    }
    
  • Semicolon separator: Uses newlines instead of spaces between semicolon-separated items in the formatter.

Debugging improvements

  • Better diagnostic reporting: Replaces the boolean coreProgramHasSuperfluousErrors with a coreDiagnostics : List DiagnosticModel that records why the Core program was suppressed. When no other diagnostics explain the suppression, these are surfaced to the user.
  • Informative invalidCoreType: Adds source and reason parameters so each suppression site provides context about what went wrong.
  • Intermediate file output: Adds processLaurelFileKeepIntermediates test helper that writes pipeline intermediate files to Build/ for debugging.
  • .gitignore: Adds Build/ directory.

Test updates

  • All expected outputs updated to match the new block formatting.
  • Minor #guard_msgs whitespace fixes.
  • Some test inputs updated to use opaque keyword where needed for the new formatting to apply correctly.

@github-actions github-actions Bot added Laurel Python github_actions Pull requests that update GitHub Actions code labels May 5, 2026
@keyboardDrummer keyboardDrummer marked this pull request as ready for review May 5, 2026 16:23
@keyboardDrummer keyboardDrummer requested a review from a team May 5, 2026 16:23
@keyboardDrummer keyboardDrummer enabled auto-merge May 5, 2026 16:23
Comment thread .github/workflows/ci.yml Outdated
@github-actions github-actions Bot added Waiting-For-Review and removed github_actions Pull requests that update GitHub Actions code labels May 5, 2026
@keyboardDrummer

Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot please resolve the build failures

@tautschnig tautschnig left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1. Scope. The PR title is accurate but understates what's inside. It contains:

  • Formatter change in Strata/DDM/Format.lean:456 — flips SepFormat.semicolon from "; " to ";\n". This is a DDM-wide change that affects any dialect using SemicolonSepBy. I searched the repo (grep -rn SemicolonSepBy Strata/Languages --include='*.st') and only Laurel currently uses it, so in practice the blast radius is the Laurel tests you're already updating — but the commit history doesn't flag this as a cross-cutting change, and anyone adding a new dialect that uses SemicolonSepBy later will inherit the vertical layout without necessarily expecting it. Worth a one-line PR-body note or a comment in Format.lean.
  • Laurel grammar change at LaurelGrammar.st:109–110 — the block and labelledBlock ops use "{\n " indent(2, stmts) "\n}" instead of the single-line form. This is the Laurel-local wrapper that, combined with (1), gives the vertical { \n … \n} shape.
  • Diagnostic-reporting refactor in LaurelToCoreTranslator.lean and LaurelCompilationPipeline.leanBool → List DiagnosticModel. Touches a different concern from the formatter change.
  • Test helper processLaurelFileKeepIntermediates in TestExamples.lean, plus .gitignore entry for Build/.
  • ~250 lines of test expected-output updates across 8+ files.

Ideally this would have been three PRs (formatter, diagnostic refactor, test helper). Given it's already cut, a structured commit log and PR body note would help reviewers separate the concerns — they're independently reviewable.

2. Silent-suppression edge case. At LaurelCompilationPipeline.lean:218:

    if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then
      let locatedDiags := translateState.coreDiagnostics.filter (·.fileRange != FileRange.unknown)
      allDiagnostics := allDiagnostics ++ locatedDiags

    let coreProgramOption :=
      if !translateState.coreDiagnostics.isEmpty then none else coreProgramOption

If translateState.coreDiagnostics is non-empty but every entry has FileRange.unknown, then:

  • locatedDiags = [], so allDiagnostics stays empty.
  • coreProgramOption := none because coreDiagnostics.isEmpty = false.
  • User sees: no diagnostics, but the translation is suppressed.

The intended rationale ("those without one are not actionable for the user") is understandable for locations, but the diagnostics still carry a message field that's strictly more useful than silence. Before this PR the behaviour was "silent suppression on coreProgramHasSuperfluousErrors = true", so this isn't a regression — it just misses an opportunity that the new design could take.

Two shapes of fix:

  • Don't filter. allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics. The user gets the message text without a location; still better than silence, and the StrataBug type tag (which every invalidCoreType entry sets) already flags these as pipeline issues rather than user errors.
  • Fallback message. Keep the filter; if locatedDiags is empty but coreDiagnostics is non-empty, synthesise a single summary diagnostic of the form "Translation suppressed with {n} internal errors without source locations: {message1}; {message2}; …".

Inline suggestion on line 215–223.

3. Duplicate test helper. processLaurelFileKeepIntermediates (plus buildDir plus the Build/ .gitignore entry) is added verbatim by PR #1113 as well. Whichever lands first, the other will have a merge conflict. Worth coordinating with the #1113 author so only one PR lands the helper (probably this one, since the PR description ties it to the diagnostic-reporting debugging work).

4. invalidCoreType default reason is noisy. LaurelToCoreTranslator.lean:78:

private def invalidCoreType (source : Option FileRange := none) (reason : String := "Type could not be translated to Core (resolution error placeholder)") : TranslateM LMonoTy := do

Every call site now passes an explicit source and reason — the only caller left using the default is… none, I think; every invalidCoreType call site in this diff passes both arguments. If that's true, dropping the defaults makes the contract explicit and eliminates a class of "I forgot to say why" callers. Low-priority stylistic nit.

5. Redundant diagnostic write in the last arm of translateType. Lines 111–113:

  | _ => do
    emitDiagnostic (diagnosticFromSource ty.source "cannot translate type to Core: not supported yet" DiagnosticType.StrataBug)
    invalidCoreType ty.source s!"cannot translate type to Core: not supported yet"

The emitDiagnostic call adds the message to state.diagnostics; the invalidCoreType call then also constructs and appends an identical message to state.coreDiagnostics. Since allDiagnostics is now non-empty (the emitDiagnostic entry), the guard at line 215 skips over the coreDiagnostics entry — so the duplicate is dead state, not user-visible duplication, but it's carrying a second copy of the same DiagnosticModel through the pipeline. Either drop the emitDiagnostic (and rely on the fallback surfacing at line 218) or have invalidCoreType only append to coreDiagnostics without constructing a fresh diagnostic when the caller already emitted one. Small cleanup; not blocking.

Comment thread Strata/Languages/Laurel/LaurelCompilationPipeline.lean
Comment thread StrataTest/Languages/Laurel/TestExamples.lean Outdated
@keyboardDrummer

keyboardDrummer commented May 5, 2026

Copy link
Copy Markdown
Contributor

1. Scope. The PR title is accurate but understates what's inside. It contains:

Ideally this would have been three PRs (formatter, diagnostic refactor, test helper). Given it's already cut, a structured commit log and PR body note would help reviewers separate the concerns — they're independently reviewable.

Are you asking me to change my approach to cutting PRs? I'm not used to a PR like this, which is of fairly manageable size and complexity, to get such feedback.

2. Silent-suppression edge case. At LaurelCompilationPipeline.lean:218:

Let's remove the filter. It makes no sense to me :) @keyboardDrummer-bot can you do that?

3. Duplicate test helper. processLaurelFileKeepIntermediates (plus buildDir plus the Build/ .gitignore entry) is added verbatim by PR #1113 as well. Whichever lands first, the other will have a merge conflict. Worth coordinating with the #1113 author so only one PR lands the helper (probably this one, since the PR description ties it to the diagnostic-reporting debugging work).

I don't think there would be a merge conflict, since the code is the same, and if there was, it would be trivially resolved by a bot. Do you really think coordination here (with the other author also being myself) is necessary?

4. invalidCoreType default reason is noisy. LaurelToCoreTranslator.lean:78:

private def invalidCoreType (source : Option FileRange := none) (reason : String := "Type could not be translated to Core (resolution error placeholder)") : TranslateM LMonoTy := do

Every call site now passes an explicit source and reason — the only caller left using the default is… none, I think; every invalidCoreType call site in this diff passes both arguments. If that's true, dropping the defaults makes the contract explicit and eliminates a class of "I forgot to say why" callers. Low-priority stylistic nit.

Okay, @keyboardDrummer-bot please remove the default.

5. Redundant diagnostic write in the last arm of translateType. Lines 111–113:

  | _ => do
    emitDiagnostic (diagnosticFromSource ty.source "cannot translate type to Core: not supported yet" DiagnosticType.StrataBug)
    invalidCoreType ty.source s!"cannot translate type to Core: not supported yet"

@keyboardDrummer-bot please remove the redundant write.

@tautschnig tautschnig left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Silent-suppression edge case in LaurelCompilationPipeline.lean (my inline on lines 215–223; thread now marked resolved on GitHub). 0b6cee46 took my option (a) — dropping the filter — then b30607ea reverted it with message "Diagnostics without a known source location are not actionable for the user. Restore the filter to fix the T1_MutableFields test failure." So:

  • The root concern (unlocated coreDiagnostics get silently dropped, the refactor's "we now have the info" promise is partially broken) still holds in the current code.
  • The test failure the revert cites reveals something I hadn't noticed: unlocated coreDiagnostics are being produced during the normal verification of T1_MutableFields, not only in the "shouldn't happen" paths. That's itself a symptom of upstream diagnostic labeling being sloppier than the filter's rationale assumes.
  • Option (b) from my earlier inline — keep the filter but synthesise a summary diagnostic when locatedDiags is empty AND coreDiagnostics is non-empty — wasn't tried. It wouldn't hit the test-failure that killed option (a), because in T1_MutableFields's case the located arm is active alongside any unlocated entries, so the fallback never fires. See the renewed inline.

The b30607ea revert commit message suggests unlocated coreDiagnostics fire during normal operation. If the T1_MutableFields test hits the surfacing path (which it would if allDiagnostics.isEmpty and any coreDiagnostics are present), then some upstream invalidCoreType call is recording a DiagnosticType.StrataBug diagnostic without a source location as part of a successful T1 compile. That's not something the filter can fix — it's a mislabelling at the source (an invalidCoreType call that shouldn't be firing, or firing without a source when one is available). Worth tracking as a follow-up: grep invalidCoreType Strata/Languages/Laurel/LaurelToCoreTranslator.lean against the T1 fixture to find which call site is the culprit.

No test or proof coverage changes from my earlier review — the suggestions I made there still apply, and the regression test I asked for would have caught exactly the case that drove the revert: a program whose suppression reason is a StrataBug without source location should emit something to the user, not silence.

Comment thread Strata/Languages/Laurel/LaurelCompilationPipeline.lean
MikaelMayer
MikaelMayer previously approved these changes May 28, 2026
Comment thread Strata/DDM/Format.lean

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖🔍 Overall a clean PR. The formatting changes are straightforward and the test updates are consistent. The coreDiagnostics refactoring is a nice improvement over the boolean flag — it gives much better debuggability. A couple of observations below.

Comment thread Strata/Languages/Laurel/LaurelToCoreTranslator.lean Outdated
Comment thread Strata/Languages/Laurel/LaurelToCoreTranslator.lean Outdated

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖🔍 The new commit (65ab6ae5) is a clean mechanical rename of SepFormat.semicolonSepFormat.semicolonNewline — looks good.

However, my two previous inline comments remain unaddressed:

  1. emitCoreDiagnostic helper — the modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [..] } pattern still appears 5 times. Extracting a helper (analogous to emitDiagnostic) would reduce repetition.

  2. Behavior change in the return-with-value case (line ~505) — on main this called emitDiagnostic (unconditionally surfacing the diagnostic) AND set the flag. The current code only appends to coreDiagnostics, meaning the diagnostic is only surfaced when allDiagnostics is otherwise empty. If another pass emits an unrelated warning, this bug indicator gets silently dropped.

Neither is blocking, but worth addressing before merge. Reviewer sign-off still needed.

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖🔍 Comment #1 (extract emitCoreDiagnostic helper) is fully addressed in commit 5c0a458 — all 5 occurrences now use the helper.

Comment #2 (behavior change in the return-with-value case): the case at line ~507 still only calls emitCoreDiagnostic d without also calling emitDiagnostic d, unlike throwStmtDiagnostic which does both. This means the diagnostic won't surface unconditionally if other diagnostics exist. If this is intentional (StrataBug diagnostics shouldn't clutter user-facing output), a brief comment explaining the choice would help future readers. Not blocking.

Reviewer sign-off still needed.

@tautschnig tautschnig left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only one remaining gap from my original review (has not been addressed so far):

No regression test for the silent-suppression case. The point of the Bool → List DiagnosticModel refactor is precisely that we now have the information that was previously thrown away. A 10-line test that constructs a Laurel input forcing invalidCoreType to fire (e.g. via a synthesised HighTypeMd whose .val matches the catch-all arm of translateType) and asserts that processLaurelFile's returned diagnostics list is non-empty would lock the contract in. Without it, a future simplification of lines 220–224 — particularly any reintroduction of the FileRange.unknown filter — silently re-introduces the silent suppression, and CI won't notice.

Proof opportunities (none currently exist for these helpers):

  • theorem emitCoreDiagnostic_appends (d s) : ((emitCoreDiagnostic d).run s).snd.coreDiagnostics = s.coreDiagnostics ++ [d] — trivial but pins the contract that emitCoreDiagnostic is purely append-only (it currently is, but a future O(1) prepend-and-reverse refactor could change that).
  • theorem coreDiagnostics_nonempty_iff_suppressed : translateState.coreDiagnostics ≠ [] ↔ translateLaurelToCore returns none ∨ pipeline surfaces them — formalises the suppression invariant the inline above relies on. Probably too much wiring for this PR; worth a follow-up.

Comment thread Strata/Languages/Laurel/LaurelCompilationPipeline.lean
@keyboardDrummer

keyboardDrummer commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

No regression test for the silent-suppression case. The point of the Bool → List DiagnosticModel refactor is precisely that we now have the information that was previously thrown away. A 10-line test that constructs a Laurel input forcing invalidCoreType to fire (e.g. via a synthesised HighTypeMd whose .val matches the catch-all arm of translateType) and asserts that processLaurelFile's returned diagnostics list is non-empty would lock the contract in. Without it, a future simplification of lines 220–224 — particularly any reintroduction of the FileRange.unknown filter — silently re-introduces the silent suppression, and CI won't notice.

The point of the Bool → List DiagnosticModel refactor is precisely that we now have the information that was previously thrown away.

The point of the refactoring is to improve the Strata developer debugging scenario in which Laurel has bugs. We don't have tests for that scenario since that requires knowingly having bugs in the code. I guess you might be able to find a way to have a test inject a bug, so that you could test the Strata bug debugging feature, but I would not say that's a requirement for this PR to be mergeable. Since the bug debugging feature is not user-facing, it's OK if it breaks.

A 10-line test that constructs a Laurel input forcing invalidCoreType to fire

I think that would amount to tests for either bugs or unsupported features. My approach so far has been not to add tests for unsupported features. It would require at least adding grammar for those features, otherwise I wouldn't be able to write the test, and that partially defeats the purpose of not yet supporting them, which is to delay the required work. It would also means the test disappears once the feature becomes supported.

@keyboardDrummer keyboardDrummer added this pull request to the merge queue Jun 1, 2026
Merged via the queue into main2 with commit a995388 Jun 1, 2026
24 checks passed
@keyboardDrummer keyboardDrummer deleted the formatting-and-debugging-improvements branch June 1, 2026 15:28
@keyboardDrummer keyboardDrummer restored the formatting-and-debugging-improvements branch June 1, 2026 15:30
@keyboardDrummer keyboardDrummer deleted the formatting-and-debugging-improvements branch June 1, 2026 15:31
shigoel pushed a commit that referenced this pull request Jun 16, 2026
## Summary

Extracts the formatting and debugging improvements from #34 into a
standalone PR.

### Formatting improvements
- **Block formatting**: Changes block output from single-line `{ stmt1;
stmt2 }` to vertical layout with `indent(2)`:
  ```
  {
    stmt1;
    stmt2
  }
  ```
- **Semicolon separator**: Uses newlines instead of spaces between
semicolon-separated items in the formatter.

### Debugging improvements
- **Better diagnostic reporting**: Replaces the boolean
`coreProgramHasSuperfluousErrors` with a `coreDiagnostics : List
DiagnosticModel` that records *why* the Core program was suppressed.
When no other diagnostics explain the suppression, these are surfaced to
the user.
- **Informative `invalidCoreType`**: Adds `source` and `reason`
parameters so each suppression site provides context about what went
wrong.
- **Intermediate file output**: Adds
`processLaurelFileKeepIntermediates` test helper that writes pipeline
intermediate files to `Build/` for debugging.
- **`.gitignore`**: Adds `Build/` directory.

### Test updates
- All expected outputs updated to match the new block formatting.
- Minor `#guard_msgs` whitespace fixes.
- Some test inputs updated to use `opaque` keyword where needed for the
new formatting to apply correctly.

---------

Co-authored-by: Remy Willems <rwillems@amazon.com>
Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
shigoel pushed a commit that referenced this pull request Jun 16, 2026
## Summary

Extracts the formatting and debugging improvements from #34 into a
standalone PR.

### Formatting improvements
- **Block formatting**: Changes block output from single-line `{ stmt1;
stmt2 }` to vertical layout with `indent(2)`:
  ```
  {
    stmt1;
    stmt2
  }
  ```
- **Semicolon separator**: Uses newlines instead of spaces between
semicolon-separated items in the formatter.

### Debugging improvements
- **Better diagnostic reporting**: Replaces the boolean
`coreProgramHasSuperfluousErrors` with a `coreDiagnostics : List
DiagnosticModel` that records *why* the Core program was suppressed.
When no other diagnostics explain the suppression, these are surfaced to
the user.
- **Informative `invalidCoreType`**: Adds `source` and `reason`
parameters so each suppression site provides context about what went
wrong.
- **Intermediate file output**: Adds
`processLaurelFileKeepIntermediates` test helper that writes pipeline
intermediate files to `Build/` for debugging.
- **`.gitignore`**: Adds `Build/` directory.

### Test updates
- All expected outputs updated to match the new block formatting.
- Minor `#guard_msgs` whitespace fixes.
- Some test inputs updated to use `opaque` keyword where needed for the
new formatting to apply correctly.

---------

Co-authored-by: Remy Willems <rwillems@amazon.com>
Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
joehendrix pushed a commit that referenced this pull request Jun 16, 2026
## Summary

Extracts the formatting and debugging improvements from #34 into a
standalone PR.

### Formatting improvements
- **Block formatting**: Changes block output from single-line `{ stmt1;
stmt2 }` to vertical layout with `indent(2)`:
  ```
  {
    stmt1;
    stmt2
  }
  ```
- **Semicolon separator**: Uses newlines instead of spaces between
semicolon-separated items in the formatter.

### Debugging improvements
- **Better diagnostic reporting**: Replaces the boolean
`coreProgramHasSuperfluousErrors` with a `coreDiagnostics : List
DiagnosticModel` that records *why* the Core program was suppressed.
When no other diagnostics explain the suppression, these are surfaced to
the user.
- **Informative `invalidCoreType`**: Adds `source` and `reason`
parameters so each suppression site provides context about what went
wrong.
- **Intermediate file output**: Adds
`processLaurelFileKeepIntermediates` test helper that writes pipeline
intermediate files to `Build/` for debugging.
- **`.gitignore`**: Adds `Build/` directory.

### Test updates
- All expected outputs updated to match the new block formatting.
- Minor `#guard_msgs` whitespace fixes.
- Some test inputs updated to use `opaque` keyword where needed for the
new formatting to apply correctly.

---------

Co-authored-by: Remy Willems <rwillems@amazon.com>
Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants