Skip to content

Inherit the state sets and methods of the supertypes' specs - #16

Merged
CatarinaGamboa merged 2 commits into
mainfrom
feat/inherited-specs
Nov 4, 2026
Merged

CatarinaGamboa merged 2 commits into
mainfrom
feat/inherited-specs

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Needed for liquid-java/vscode-liquidjava#173: a state error on a method declared in a subclass's spec, with the state set in a superclass's spec, shows no usable diagram.

Why

parseAll(uri, className) read one type. For java.awt.Frame, whose @StateSet is in the java.awt.Window spec, it found no state set, so there was no diagram. The Window diagram had no setUndecorated.

What

parseAll(List<String> uris, String className) builds one model from several files and merges the specs of the class's supertypes. It follows the rule the verifier uses for state hints since liquid-java/liquidjava#464 (StateMachineBuilder):

  • Supertypes. The class's superclasses, then the interfaces they implement, nearest first. Same walk as the verifier's supertypesOf. JDK classes resolve by reflection.
  • State sets. The class's own first, then its supertypes', nearest first. That is the order of the verifier's StateErrorContext states, so stateSet indexes match.
  • Methods. The class's own refinements, then inherited ones. An inherited refinement is left out when:
    • a nearer spec refines the same method (same name and parameter types), or
    • the nearest type that declares the method is not the spec's class (e.g. Frame redeclares addNotify, so a Window spec's addNotify does not apply), or
    • not every caller can call it (not public).
  • Constructors and factories. Only the class's own.

parseAll(uri, className) now calls it with one file. A file with only one type skips the hierarchy walk, so the editor's own-file diagrams are unchanged. Files that no longer exist are skipped.

Issue example (Window and Frame specs, both in one file or in two), parseAll(..., "java.awt.Frame"):

from to method
notDisplayable notDisplayable setUndecorated
displayable, notDisplayable notDisplayable dispose
displayable, notDisplayable displayable pack

Initial: new Frame(String) → notDisplayable. addNotify and Window(Frame) from the Window spec are left out.

Tests

mvn test: 32 tests, 0 failures, run locally. New:

  • testStateSetAndMethodsInheritedFromTheSuperclassSpec: one file, with addNotify left out on Frame and kept on Window.
  • testSpecsInheritedFromOtherFiles: specs in two files; the subclass file alone still gives nothing.
  • testOverriddenAndPackagePrivateInheritedMethods: source classes; the child's override replaces the parent's, and a package-private parent method is left out.
  • testMethodsInheritedFromASpecWithoutStateSet: JFrame over a method-only Frame spec and Window's state set.
  • testMissingFileDoesNotDiscardTheOthers: a file that no longer exists is skipped.

Release

vscode-liquidjava needs this released as liquidjava-fsm 0.2.3. Its PR bumps to that version.

🤖 Generated with Claude Code

A class whose @stateset is in a superclass's spec, such as java.awt.Frame
with the displayable states declared for java.awt.Window, had no state
machine, and the superclass's machine did not have the subclass's methods.

parseAll(List<String> uris, String className) builds one model from
several files and merges the specs of the class's supertypes in them, as
the verifier does when it builds a receiver's machine: the class's own
state sets first, then those of its supertypes, nearest first. A call
uses the class's own refinement of a method, otherwise that of the
nearest type that declares it, so an inherited refinement is left out
when a nearer spec refines the method, when a nearer type redeclares it,
or when not every caller can call it. Constructors stay the class's own.
parseAll(uri, className) does the same for the specs in one file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file that no longer exists made the model fail to build, which
discarded the specs in the other files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CatarinaGamboa
CatarinaGamboa merged commit e5099f0 into main Nov 4, 2026
2 checks passed
CatarinaGamboa added a commit to liquid-java/vscode-liquidjava that referenced this pull request Nov 10, 2026
…'s spec (#174)

Closes #173.

Uses liquidjava-fsm 0.2.3 (which includes
[liquidjava-fsm#16](liquid-java/liquidjava-fsm#16))
and verifier 0.0.37, both on main since #179. Rebased onto main on
2026-11-10: the `server/pom.xml` bump is gone; server tests pass (39
with #178 merged in too).

## Why
A state error can be on a method declared in a subclass's spec
(`Frame.setUndecorated`) while the state set is declared in a
superclass's spec (`Window`). In that case the State Machine view could
not explain the error:
- With verifier 0.0.36 (no state context), it showed `Window`'s diagram
without `setUndecorated` and marked no states (the issue's screenshot).
- With verifier main (liquid-java/liquidjava#464), the context names
`java.awt.Frame`. The parser then found `Frame`'s spec, which has no
`@StateSet`, so the error had no diagram at all.

## What
- **liquidjava-fsm#16.** The parser merges the specs of a class's
supertypes, following the rule from liquidjava#464: state sets of the
class and then of its supertypes, nearest first; for each method, the
class's own refinement, otherwise that of the nearest type that declares
it. The details are in that PR.
- **`StateRefinementErrorDTO`.** It parses the diagrams for the
context's `receiverClass`. It reads the declaration's file plus the
files of the state sets and state refinements the verifier registered
(`ContextHistory`), so specs split across files are found, including a
supertype spec with methods but no state set. Without a context it
behaves as before (first class of the declaration's file).
- **Known limit.** A project class that extends an unannotated project
class in a file the verifier registered nothing for stops the walk
there, so that class's diagram leaves out the states further up. The
parser only sees the spec files.

No verifier change is needed. liquidjava#464 already sends the context
with the receiver's states for this case. The view needs a verifier
release with #464 (after 0.0.36). With 0.0.36 nothing changes for this
case: that verifier sends no context, so the view still shows the first
class of the declaration's file (`Window` in the issue's file).

## Screenshots
Both columns use verifier main @ 9e4b5e64.

| | Before (main) | After |
|---|---|---|
| Issue reproducer (`host.pack(); host.setUndecorated(true);`) | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/174/before-issue.png"
width="380"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/174/after-issue.png"
width="380"> |
| Study exercise A1_09 (LITIENGINE `GameWindow`) | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/174/before-a1-09.png"
width="380"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/174/after-a1-09.png"
width="380"> |

In A1_09 the actual state shows as "unknown" because the verifier finds
both `displayable` and `notDisplayable` possible (it depends on the
path). `notDisplayable` is marked as the expected state, and
`setUndecorated` is the failing call.

I did not take a split-files screenshot. With `FrameRefinements.java`
read before `WindowRefinements.java`, the verifier stops with
liquid-java/liquidjava#461 before it reaches the state error.
`StateRefinementErrorDTOTest` covers that layout.

## Tests
- server `mvn test`: 39 passed, 0 failures (liquidjava-fsm 0.2.3 built
locally from #16, verifier 0.0.36). New `StateRefinementErrorDTOTest`:
- Specs in two files: the diagram is `java.awt.Frame`, with
`setUndecorated`, `dispose` and `pack` on `Window`'s states;
`displayable` is marked invalid and `notDisplayable` accepted.
- Without a context: the first class of the declaration's file, as
before.
- A `JFrame` spec over a method-only `Frame` spec and `Window`'s state
set: all their methods are drawn.
- The verifier run on that fixture: the files of all three specs are
selected.
- client `npm run test:unit`: 123 passed. `npm run test:typecheck`:
clean. `npm test` (VS Code): 9 + 1 passing.

## Overlap with #172
None. #172 changes only the client webview. This PR changes only the
server and its fsm dependency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant