|
| 1 | +# Emitting the `#SortParam` sentinel to Kore |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Not implemented. |
| 6 | +`_ksort_to_kore` raises a clear error when it encounters the sentinel, rather than producing invalid Kore. |
| 7 | +This document describes the full fix so it can be implemented and validated against real Kore output later. |
| 8 | + |
| 9 | +## Background |
| 10 | + |
| 11 | +Some ML-predicate productions have a result sort that cannot be inferred from their arguments. |
| 12 | +The canonical example is `#Equals{S1, S2}(S1, S1)`: `S1` is determined by the arguments, but `S2` (the result sort) is context-dependent. |
| 13 | +When `KDefinition.add_sort_params` fills in a partially-applied ML-predicate label, it can determine `S1` but not `S2`, so it fills `S2` with the sentinel sort `KSort('#SortParam')` (see `add_sort_params` in `src/pyk/kast/outer.py`). |
| 14 | + |
| 15 | +The Java frontend (`org.kframework.compile.AddSortInjections`) does the same thing, but with two differences that matter for Kore emission: |
| 16 | + |
| 17 | +- It generates a *fresh, uniquely named* sentinel per unresolved position: `#SortParam{Q0}`, `#SortParam{Q1}`, …. |
| 18 | +- It collects those `Qn` names during the traversal and declares them as universally-quantified sort variables on the resulting axiom (surfaced in the `sortParams(...)` rule attribute, and as sort parameters on the Kore `axiom{...}`). |
| 19 | + |
| 20 | +## The problem in pyk |
| 21 | + |
| 22 | +`_ksort_to_kore` (in `src/pyk/konvert/_kast_to_kore.py`) is a context-free, term-level converter: it maps every `KSort` to `SortApp('Sort' + name)`. |
| 23 | +For the sentinel this yields `SortApp('Sort#SortParam')`, which is doubly wrong: |
| 24 | + |
| 25 | +- `#` is not a legal Kore identifier character, so the `SortApp`/`Id` constructor raises `ValueError: Expected identifier`. |
| 26 | +- Even if it were constructible, a bare sort application referencing a sort that exists in no definition is not what the sentinel means — it must be a *bound sort variable*. |
| 27 | + |
| 28 | +`krule_to_kore` builds an axiom by calling `kast_to_kore` several independent times (lhs body, rhs body, each constraint), and sets the axiom's sort variables (`Axiom.vars`) to `()` for ordinary rules or `(SortVar('R'),)` for functional rules. |
| 29 | +There is no mechanism to (a) emit the sentinel as a sort variable, (b) give it a unique name, or (c) collect it back up and bind it on the axiom. |
| 30 | + |
| 31 | +## Reachability |
| 32 | + |
| 33 | +In the normal pyk flow this path is rarely hit. |
| 34 | +ML predicates constructed through the prelude (`mlEquals`, `mlEqualsTrue`, `bool_to_ml_pred`, …) always pass explicit sort params (e.g. `KLabel('#Equals', arg_sort, sort)`), so they hit the early return in `add_sort_params` and never produce the sentinel. |
| 35 | +The sentinel only arises for ML predicates that reach `add_sort_params` with *missing* sort params (e.g. frontend-loaded or hand-constructed terms) and whose result sort cannot be inferred. |
| 36 | + |
| 37 | +## Proposed fix |
| 38 | + |
| 39 | +The goal is to mirror the Java behaviour: each unresolved result sort becomes a uniquely-named, universally-quantified Kore sort variable on the enclosing axiom. |
| 40 | + |
| 41 | +1. **Unique sentinels.** |
| 42 | + Replace the single bare `KSort('#SortParam')` with parametric sentinels `KSort('#SortParam', (KSort('Q0'),))`, `KSort('#SortParam', (KSort('Q1'),))`, …. |
| 43 | + This removes the current "at most one unbound param" restriction (the `NotImplementedError` guard in `add_sort_params`) and lets distinct unresolved positions stay distinct. |
| 44 | + Generating fresh names requires a counter; since `add_sort_params` runs per `kast_to_kore` call, the counter must be threaded from `krule_to_kore` (or `add_sort_params` must accept a starting index and return the names it used) so that names are unique *across* the several `kast_to_kore` calls that make up one axiom. |
| 45 | + |
| 46 | +2. **Emit sentinels as sort variables.** |
| 47 | + Teach `_ksort_to_kore` (or the `_kapply_to_pattern` sort-emission path) to map `#SortParam{Qn}` to `SortVar('Qn')` instead of a `SortApp`. |
| 48 | + |
| 49 | +3. **Bind them on the axiom.** |
| 50 | + `krule_to_kore` collects every `SortVar` emitted from a sentinel across all of its `kast_to_kore` calls and adds them to `Axiom.vars` (alongside `R` for functional rules), and to the `sortParams(...)` attribute, matching the Java output. |
| 51 | + |
| 52 | +## Validation |
| 53 | + |
| 54 | +This change alters Kore axiom output and must be validated end-to-end, not just at the unit level: |
| 55 | + |
| 56 | +- The konvert integration tests (`src/tests/integration/konvert/`), which require the K toolchain. |
| 57 | +- The pyk regression suite (`pyk/regression-new/`), comparing emitted Kore against the Java backend for a definition that actually exercises an uninferable ML-predicate result sort. |
| 58 | + |
| 59 | +A minimal repro to build first: a rule (loaded from a compiled definition, not constructed via the prelude) containing an ML predicate whose result sort is not determined by its arguments, asserting the emitted axiom binds a sort variable rather than referencing `Sort#SortParam`. |
0 commit comments