Class ConstraintValueSearch

java.lang.Object
org.ek9lang.compiler.symbolic.ConstraintValueSearch

public final class ConstraintValueSearch extends Object
Finds values a constrained type can actually hold, by PROBING the constraint rather than reading it — the recovery path for when every value we knew how to guess turned out to be inadmissible.

Why probing, when ConstraintWindows already reads the constraint? The fold is exact but narrow: Integer bases, and-combinations, bound operators only. Everything else — a Float range, a Date window, an or, a composite — folds to nothing, and the generator falls back to spraying the base type's general literal pool and letting E08260 prune. That works only while the pool HAPPENS to contain an admissible value. When it does not, the run produces no set value at all, and producing nothing is itself the signal: every candidate sat outside the constraint, so the thing to do is go and look for where the admissible values actually are.

The oracle makes this cheap and safe. CompileTimeConstraintEvaluator is a reflective interpreter over the real EK9 standard library — it builds subject and literal with the same _of(String) factory the bytecode generator emits, so its verdict is byte-for-byte faithful to runtime for ANY constrainable base type, with no per-type special-casing. Two consequences shape this class:

  • A probe costs a reflective call, not a compile pass — so we can afford hundreds, and we only spend them on the failure path.
  • Every value returned here has been verified PASS before it is returned. The search can therefore never widen the population into values that will be pruned. Too narrow costs coverage; too wide is impossible by construction — the same one-sided posture the rest of the generator takes.

Where the seeds come from — the constraint names them. A blind numeric hunt cannot find a narrow window far from the origin, which is why bisection alone is not enough to start. But the constraint's own literals ARE the interesting coordinates: >= 1000.0 and <= 2000.0 carries 1000.0 and 2000.0. Probing those, their neighbours (for a strict bound like > 5, where the literal itself fails and 6 succeeds) and their pairwise midpoints (for a strict bound on a continuous base, where > 0.0 and < 1.0 fails at both ends and passes at 0.5) yields a verified seed for essentially any bound-based constraint — on Integer, Float, Date, Money or anything else, because the seeds are literal TEXT handed to a type-agnostic evaluator.

Only once a seed is verified does bisection earn its place: from a value known to be inside, widen outward to the true edges. That is sound where a blind bisection is not, because it starts from a point in the admissible set rather than hoping to land in one.

🔑 A suffixed value varies along TWO axes, and only one of them may move. Money is an amount AND a currency; Dimension is a magnitude AND a unit. Comparing across the suffix is not decidable at compile time — 10cm against >= 500mm evaluates UNKNOWN, not FAIL, even though 10cm is plainly below 500mm — so a candidate carrying a different suffix from the constraint escapes the E08260 filter and then Panics at runtime. Everything generated here therefore pins the suffix to the one the constraint itself declared and moves only the numeric part. The type's own ++ / -- already behave this way (Dimension._inc re-attaches its suffix, Money._inc adds one minor unit of its own currency), which is why stepping is preferred over arithmetic here.

  • Constructor Details

    • ConstraintValueSearch

      public ConstraintValueSearch()
  • Method Details

    • admissibleValues

      public List<String> admissibleValues(String baseTypeFqn, ConstraintExpr constraint)
      Values of baseTypeFqn that provably satisfy constraint, most useful first, or an empty list when probing found none.

      An empty result does NOT prove the type is uninhabitable — only that this bounded search did not find a witness. The two are different claims and must not be conflated.

      Parameters:
      baseTypeFqn - the constrained base type's fully qualified name
      constraint - the constraint to satisfy
      Returns:
      verified-admissible literal texts