Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions slang/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ inputs:
reporters (e.g., spawned by a matrix) may overwrite each others results. Defaults to slang.'
required: false
default: 'slang'
fail-on-error:
description: 'Fail the job when slang reports an error. Defaults to false, which keeps the
action a pure annotator: run_slang.py exits 0 regardless of compile errors so reviewdog
can annotate them. Set to true to use the action as a gate.'
required: false
default: 'false'
pyslang-version:
description: 'pyslang version to install, as an exact version number (e.g. "11.0.0").
pyslang bundles the slang engine and is released in lockstep with it, so this single
Expand Down Expand Up @@ -64,3 +70,11 @@ runs:
-reporter=${{ inputs.reviewdog-reporter }} \
-filter-mode=nofilter \
-fail-level=error

# After reviewdog, so the diagnostics are still annotated on a failing run.
# Read from the diagnostics rather than reviewdog's exit status: reviewdog
# needs a check-write token, which a pull request from a fork does not get.
- name: Fail on slang errors
if: inputs.fail-on-error == 'true'
shell: bash
run: uv run ${{ github.action_path }}/check_diags.py slang_diags.json
51 changes: 51 additions & 0 deletions slang/check_diags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
#
# Copyright 2026 ETH Zurich and University of Bologna.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
# Daniel Keller <dankeller@iis.ee.ethz.ch>

"""Exit non-zero when a slang diagnostic file contains errors.

Read straight from the diagnostics rather than from reviewdog's exit status:
reviewdog needs a token with check-write permission, which a pull request from
a fork does not get, so a gate built on it would pass silently exactly where a
public gate is worth the most.
"""

import json
import sys


def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <diagnostics_json>", file=sys.stderr)
sys.exit(1)

path = sys.argv[1]
try:
with open(path, encoding="utf-8") as handle:
diagnostics = json.load(handle)
except FileNotFoundError:
# slang always writes the file, so its absence means the run never
# happened; failing here keeps a missing run from reading as success.
print(f"error: no diagnostics at {path}; slang did not run", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as exc:
print(f"error: {path} is not valid JSON: {exc}", file=sys.stderr)
sys.exit(1)

errors = [d for d in diagnostics if d.get("severity", "").lower() == "error"]
if errors:
print(f"slang reported {len(errors)} error(s):", file=sys.stderr)
for diag in errors:
location = diag.get("location", "")
print(f" {location}: {diag.get('message', '')}", file=sys.stderr)
sys.exit(1)

print(f"slang: no errors in {len(diagnostics)} diagnostic(s)")


if __name__ == "__main__":
main()