Skip to content

Commit ffd51c6

Browse files
Support length on arrays of any element type (#357)
Fixes #355. ## Problem The builtin `length` was declared as `(declare-fun length ((Array Int Int)) Int)`, for `int[]` only. Any other array's `.length` in the context (a `for (int i = 0; i < names.length; i++)` loop over a `String[]`) made every Z3 query in that scope crash with `Sort mismatch at argument #1 for function (declare-fun length ((Array Int Int)) Int) supplied sort is |java.lang.String[]|`. ## Change `TranslatorToZ3.makeFunctionInvocation` routes `length` to `makeLength`, which uses the builtin declaration for `int[]` and otherwise declares (once per sort, cached) a `length` overload whose domain is the array's sort. ## Tests - `CorrectStringArrayLength`: a loop bounded by `names.length` over a refined `String[]`, plus `length(names) > 0` flowing into `_ > 0`; crashed on main, now passes. - `ErrorStringArrayLength`: `length(names) >= 0` does not give `_ > 0`; crashed on main, now the Refinement Error. - `mvn test` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a4fbebe commit ffd51c6

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class CorrectStringArrayLength {
6+
7+
static int nonNegative(@Refinement("_ >= 0") int x) {
8+
return x;
9+
}
10+
11+
public static int count(@Refinement("length(names) > 0") String[] names) {
12+
int n = 0;
13+
for (int i = 0; i < names.length; i++) {
14+
n = nonNegative(i - i);
15+
}
16+
@Refinement("_ > 0")
17+
int size = names.length;
18+
return n + size;
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class ErrorStringArrayLength {
6+
7+
public static void first(@Refinement("length(names) >= 0") String[] names) {
8+
@Refinement("_ > 0")
9+
int size = names.length; // Expect: Refinement Error
10+
}
11+
}

‎liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class TranslatorToZ3 implements AutoCloseable {
3939
private final Map<String, List<Expr<?>>> varSuperTypes = new HashMap<>();
4040
private final Map<String, AliasWrapper> aliasTranslation = new HashMap<>(); // this is not being used
4141
private final Map<String, FuncDecl<?>> funcTranslation = new HashMap<>();
42+
private final Map<Sort, FuncDecl<?>> lengthBySort = new HashMap<>();
4243
private final Map<String, Expr<?>> funcAppTranslation = new HashMap<>();
4344
private final Map<Expr<?>, String> exprToNameTranslation = new HashMap<>();
4445
/**
@@ -163,6 +164,8 @@ public Expr<?> makeFunctionInvocation(String name, Expr<?>[] params) throws LJEr
163164
return makeStore(params);
164165
if (name.equals("getFromIndex"))
165166
return makeSelect(params);
167+
if (name.equals("length") && params.length == 1)
168+
return makeLength(params[0]);
166169
FuncDecl<?> fd = funcTranslation.get(name);
167170
if (fd == null)
168171
fd = resolveFunctionDecl(name, params);
@@ -185,6 +188,20 @@ public Expr<?> makeFunctionInvocation(String name, Expr<?>[] params) throws LJEr
185188
return app;
186189
}
187190

191+
/**
192+
* Applies {@code length} to an array of any element type: the builtin declaration covers {@code int[]} only, so
193+
* other array sorts get their own overload, declared on first use.
194+
*/
195+
private Expr<?> makeLength(Expr<?> array) {
196+
Sort sort = array.getSort();
197+
FuncDecl<?> fd = funcTranslation.get("length");
198+
if (!fd.getDomain()[0].equals(sort))
199+
fd = lengthBySort.computeIfAbsent(sort, s -> z3.mkFuncDecl("length", s, z3.getIntSort()));
200+
Expr<?> app = z3.mkApp(fd, array);
201+
funcAppTranslation.put(buildFunctionLabel("length", new Expr<?>[] { array }), app);
202+
return app;
203+
}
204+
188205
/**
189206
* Gets function declarations when an exact qualified name lookup fails. Tries to match by simple name and number of
190207
* parameters, preferring an exact qualified-name match if found among candidates; otherwise returns the first

0 commit comments

Comments
 (0)