Navigation

STLC Refinements and Diagnostics

The refinement surface gives a source-language spelling to the HOAS refinement carrier and preserves diagnostic intent. A missing annotation, an ordinary type mismatch, and a failed refinement proof are distinct failures.

Refinement values

A refinement type is represented as a dependent pair whose second component is a positive decision. The surface refine form packages the witness and proof into that carrier.

atLeastOneNat =
  refinement "AtLeastOne" H.nat (n: H.le one n);

oneLeOne = H.leSS H.leZ;
oneAtLeastOne = refine atLeastOneNat one oneLeOne;

stlcRefinementValueChecks =
  let
    r = check oneAtLeastOne atLeastOneNat
      { path = [ "refinement" "value" ]; };
  in
  !(r ? error) && r.tag == "pair";

Refinement failure

If the witness and proof do not match, the surface reports a refinement-specific diagnostic instead of exposing the raw Sigma mismatch as the main error.

zeroWithOneProof = refine atLeastOneNat H.zero oneLeOne;

stlcBadRefinementHasSurfaceDiagnostic =
  let
    position = { path = [ "bad" "refinement" ]; };
    r = check zeroWithOneProof atLeastOneNat position;
  in
  (r ? error)
  && (r.kind or null) == "stlc.refinement-failure"
  && (r.position or null) == position;

Diagnostic classes

The examples keep three failure classes separate: unsolved implicit information, ordinary checker errors, and refinement failures. That gives editors and command-line tools enough structure to suggest the right repair.

stlcDiagnosticUnsolvedImplicitIsDistinct =
  let
    position = { path = [ "diagnostic" "implicit" ]; };
    r = elaborate (Core.holeLam "x" (x: Core.var x)) position;
  in
  (r ? error)
  && (r.kind or null) == "surface.unsolved-implicit"
  && (r.position or null) == position;

stlcDiagnosticRefinementFailureIsDistinct =
  let
    position = { path = [ "diagnostic" "refinement" ]; };
    r = check zeroWithOneProof atLeastOneNat position;
  in
  (r ? error)
  && (r.kind or null) == "stlc.refinement-failure";