This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LiquidJava is an additional type checker for Java that adds liquid types (refinements) and typestates on top of standard Java. Users annotate Java code with @Refinement, @StateRefinement, @StateSet etc. (from liquidjava-api); the verifier parses the program with Spoon, translates refinement predicates to SMT, and discharges verification conditions with Z3.
Requires Java 20+ and Maven 3.6+ (the parent POM declares 1.8 source/target, but the verifier module overrides to 20).
This is a Maven multi-module build (pom.xml is the umbrella):
liquidjava-api— published annotations (@Refinement,@RefinementAlias,@StateRefinement,@StateSet, ghost functions). Stable artifact users depend on.liquidjava-verifier— the actual checker (Spoon processor + RJ AST + SMT translator). Published asio.github.liquid-java:liquidjava-verifier.liquidjava-example— sample programs and the test suite undersrc/main/java/testSuite/. The verifier's tests scan this directory.
Verifier package map (liquidjava-verifier/src/main/java/liquidjava/):
api/— entrypoints;CommandLineLauncheris the CLI main.processor/— Spoon processors.RefinementProcessororchestrates;refinement_checker/containsRefinementTypeChecker,MethodsFirstChecker,ExternalRefinementTypeChecker, plusgeneral_checkers/andobject_checkers/for typestate.ast/— AST of the Refinements Language (RJ).rj_language/— parser from refinement strings to RJ AST.smt/— Z3 translation (TranslatorToZ3,ExpressionToZ3Visitor,SMTEvaluator,Counterexample).errors/,utils/,diagnostics/.
Build / install everything:
mvn clean installRun the test suite (verifier module, runs whole testSuite/ dir):
mvn testRun a single test method (JUnit 4/5 mix — both work via Surefire):
mvn -pl liquidjava-verifier -Dtest=TestExamples test
mvn -pl liquidjava-verifier -Dtest=TestExamples#testMultiplePaths testVerify a specific file/directory from CLI (uses the liquidjava script in repo root, macOS/Linux):
./liquidjava liquidjava-example/src/main/java/testSuite/CorrectSimpleAssignment.javaEquivalent raw form:
mvn exec:java -pl liquidjava-verifier \
-Dexec.mainClass="liquidjava.api.CommandLineLauncher" \
-Dexec.args="/path/to/file_or_dir"Code formatting runs automatically in the validate phase via formatter-maven-plugin (configured for Java 20 in liquidjava-verifier/pom.xml); no separate lint command.
Tests are discovered by TestExamples#testPath (parameterized) under liquidjava-example/src/main/java/testSuite/:
- Single-file cases: filename starts with
Correct…orError…. - Directory cases: directory name contains the substring
correctorerror. - Anything else is ignored (so helper sources can live alongside).
- Expected errors for a failing case are declared with inline
// <Error Title>comments on the line where each error should be reported (regex//\s*(.*?\bError\b), case-insensitive — seeTestUtils#getExpectedErrorsFromFile). Both the title and the line number must match, and the count of comments must equal the count of reported errors. Directory cases work the same way: the scanner walks every file in the directory; there are no.expectedfiles.
When adding new test cases, place them under liquidjava-example/src/main/java/testSuite/ following the naming rules above — that is the only way they get picked up.
- Two-pass typechecking.
MethodsFirstCheckercollects method signatures and refinement contracts beforeRefinementTypeCheckerwalks bodies, so forward references and recursion resolve. Edits to one usually need a matching change in the other. - Refinement string → AST → Z3. A
@Refinement("a > 0")string flows:rj_languageparser →astnodes →smt/TranslatorToZ3/ExpressionToZ3Visitor. New predicate forms generally require touching all three. - External refinements.
ExternalRefinementTypeCheckerplus*Refinements.javacompanion files specify contracts for third-party APIs without modifying their sources. Theco-specifying-liquidjavaskill covers this workflow. - Typestate lives in
processor/refinement_checker/object_checkers/and uses@StateRefinement/@StateSetfrom the API. Ghost-state predicates flow through the same SMT pipeline as value refinements. - Z3 dependency. The verifier shells out to Z3 via JNI bindings; failures often surface as
SMTResulterrors or counterexamples, not Java exceptions.