GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Named state sets, so a spec can refer to a whole set (e.g. old(phase(this))) · Issue #435 · liquid-java/liquidjava

Named state sets, so a spec can refer to a whole set (e.g. old(phase(this))) #435

New issue
Copy link
New issue
Copy link
Open
Open
Named state sets, so a spec can refer to a whole set (e.g. old(phase(this)))#435
Copy link
Labels
enhancementNew feature or requestNew feature or requesterror messagesRelated to error messagesRelated to error messagesstudyNeeded before the error-message user studyNeeded before the error-message user study

Description

@CatarinaGamboa
CatarinaGamboa
opened on Oct 28, 2026
Issue body actions

Motivation

Each @StateSet becomes an anonymous ghost function (state1, state2, ...). After a call, AuxStateHandler.getMissingStates adds stateN(this) == stateN(old(this)) for every set the to predicate does not mention, so unmentioned sets keep their value. That works, but the spec author cannot name a set, which causes three problems.

Running example: an UndoManager spec with three orthogonal sets. The phase decides addEdit/setLimit; whether there is something to undo or redo decides undo()/redo(), and canUndo()/canRedo() are guards.

@StateSet({"start", "canEdit", "end"})
@StateSet({"undoable", "notUndoable"})
@StateSet({"redoable", "notRedoable"})
...
@StateRefinement(from="undoable(this)", to="notUndoable(this) && redoable(this)")
void undo();

@StateRefinement(from="undoable(this)", to="return && undoable(this)")
@StateRefinement(to="return ? undoable(this) : notUndoable(this)")
boolean canUndo();

1. A set mentioned in one branch is dropped in the other

A set counts as "mentioned" if any of its states appears anywhere in to. With to="return ? undoable(this) : true", the undo set is not carried over in the false branch: it becomes unconstrained instead of keeping its previous value. Verified with verifier 0.0.36 (addEdit; boolean b = um.canUndo(); um.undo();). The found predicate carries state1(um⁸⁵) == state1(um⁸³) && state3(um⁸⁵) == state3(um⁸³) but has no equality for state2. There is no way to write "in this branch, keep the set as it was".

2. Error messages leak the internal names

State Refinement Error: found canEdit(um⁸³) && undoable(um⁸³) && notRedoable(um⁸³) && (canUndo⁸⁴ ? undoable(um⁸⁵) : notUndoable(um⁸⁵)) && state1(um⁸⁵) == state1(um⁸³) && state3(um⁸⁵) == state3(um⁸³) but expected undoable(um⁸⁵)

state1 and state3 appear nowhere in the spec.

3. Hints multiply all sets together

For an unknown receiver (a field or a getter result), the hint lists the cross product of all sets, although the failing call only needs one:

The state of undoManager is unknown here. To get it to start and undoable and redoable or start and notUndoable and redoable or canEdit and undoable and redoable or canEdit and notUndoable and redoable or end and undoable and redoable or end and notUndoable and redoable, use: undoManager.undo() (from start and undoable and notRedoable or canEdit and undoable and notRedoable or end and undoable and notRedoable)

redo() only needs redoable. A hint per named set could say "undoManager is not known to be redoable; check canRedo()". The guard canRedo() is missing from the hint entirely, because it is a from-less refinement whose result decides the state (related: liquid-java/liquidjava-fsm#14).

Proposal

Allow an optional name on a state set, which can be used as a function in predicates:

@StateSet(name="phase", value={"start", "canEdit", "end"})
@StateSet(name="undo",  value={"undoable", "notUndoable"})
@StateSet(name="redo",  value={"redoable", "notRedoable"})

@StateRefinement(to="return ? undoable(this) : undo(this) == old(undo(this))")
boolean canUndo();
  • undo(this) == old(undo(this)) keeps a set explicitly, per branch (fixes 1).
  • Messages print phase(um) / undo(um) instead of state1(um) (fixes 2).
  • Hints can name only the set the failing precondition needs (helps 3).
  • Return one machine per @StateSet liquidjava-fsm#8 ("one machine per @stateset") could use the names as machine titles.

Unnamed sets would keep working as today, with the generated name.

Context: found while writing UndoManager specs for LiquidJava study exercises (JabRef's EditAction, where undo()/redo() on a manager from a getter must be guarded by canUndo()/canRedo()).

Activity

  1. CatarinaGamboa
    added
    enhancementNew feature or requestNew feature or request
    error messagesRelated to error messagesRelated to error messages
    on Oct 28, 2026
  2. CatarinaGamboa
    added
    studyNeeded before the error-message user studyNeeded before the error-message user study
    on Nov 6, 2026
  3. CatarinaGamboa
    added a commit that references this issue on Nov 9, 2026

    Named state sets: optional name on @stateset (#498)

    2c4f484
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestNew feature or requesterror messagesRelated to error messagesRelated to error messagesstudyNeeded before the error-message user studyNeeded before the error-message user study

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.