Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions basex-core/src/main/java/org/basex/query/func/Closure.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public Closure(final InputInfo info, final Expr expr, final Params params, final
this.params = params;
this.anns = anns;
this.vs = vs;
this.global = global == null ? new HashMap<>() : new HashMap<>(global);
this.global = global == null ? new HashMap<>() : new LinkedHashMap<>(global);
props = new FlagCache(flag -> {
for(final Expr ex : this.global.values()) {
if(ex.has(flag)) return true;
Expand Down Expand Up @@ -197,7 +197,7 @@ public Expr optimize(final CompileContext cc) {
cc.info(OPTINLINE_X, entry);
for(final Entry<Var, Expr> expr2 : cl.global.entrySet()) {
final Var var2 = cc.copy(expr2.getKey(), null);
if(add == null) add = new HashMap<>();
if(add == null) add = new LinkedHashMap<>();
add.put(var2, expr2.getValue());
expr2.setValue(new VarRef(cl.info, var2).optimize(cc));
}
Expand Down Expand Up @@ -335,15 +335,15 @@ public Expr inline(final InlineContext ic) throws QueryException {
public Expr copy(final CompileContext cc, final IntObjectMap<Var> vm) {
final VarScope scope = new VarScope();

final HashMap<Var, Expr> outer = new HashMap<>();
final HashMap<Var, Expr> outer = new LinkedHashMap<>();
global.forEach((key, value) -> outer.put(key, value.copy(cc, vm)));

cc.pushScope(scope);
try {
final IntObjectMap<Var> innerVars = new IntObjectMap<>();
vs.copy(cc, innerVars);

final HashMap<Var, Expr> bindings = new HashMap<>();
final HashMap<Var, Expr> bindings = new LinkedHashMap<>();
outer.forEach((key, value) -> bindings.put(innerVars.get(key.id), value));

final Var[] prms = params.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private Expr closure(final Expr func, final FuncType ft, final int nargs,
cc.pushScope(vs);
try {
// the function and the supplied arguments are evaluated once, when the closure is created
final HashMap<Var, Expr> global = new HashMap<>();
final HashMap<Var, Expr> global = new LinkedHashMap<>();
final Var fn = vs.addNew(new QNm(FUNCTION), func.seqType(), cc.qc, info);
global.put(fn, func);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private QNm[] inScopeNames() {
* @return global map or {@code null}
*/
public HashMap<Var, Expr> pushContext(final boolean global) {
final HashMap<Var, Expr> map = global ? new HashMap<>() : null;
final HashMap<Var, Expr> map = global ? new LinkedHashMap<>() : null;
vars.add(new VarContext(map));
return map;
}
Expand Down
35 changes: 35 additions & 0 deletions basex-core/src/test/java/org/basex/query/ast/FuncItemTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import static org.basex.query.QueryError.*;
import static org.basex.query.func.Function.*;
import static org.junit.jupiter.api.Assertions.*;

import org.basex.*;
import org.basex.query.*;
import org.basex.query.expr.*;
import org.basex.query.func.*;
import org.basex.query.value.item.*;
Expand Down Expand Up @@ -745,4 +747,37 @@ declare record local:rec(i as xs:integer, inc as fn(local:rec) as local:rec := f
query(java + "let $f as fn() as xs:string* := $j return count($f())", 0);
error(java + "let $f as fn() as xs:integer := $j return $f()", INVTYPE_X);
}

/**
* Plans of closures with several captured variables are identical across compilations.
* @throws Exception exception
*/
@Test public void closurePlanOrder() throws Exception {
final String vars = "let $a := <a/>, $b := <b/>, $c := <c/>, $d := <d/> ";
final String[] queries = {
// parsed closure
vars + "return fn() { ($a, $b, $c, $d) }",
"let $n := <n/>, $m := <m/>, $o := <o/> return "
+ "deep-equal(fn($x) { fn() { $x, $n, $o } }, fn($y) { fn() { $y, $m, $o } })",
// nested closure is inlined into the surrounding closure
vars + "let $f := fn() { ($a, $b, $c, $d) } return fn() { $f() }",
// closure is copied when the function body is inlined
"declare %basex:inline(1000) function local:f($x) { "
+ "let $a := <a/>, $b := <b/>, $c := <c/> return fn() { ($a, $b, $c, $x) } }; "
+ "local:f(<x/>), local:f(<y/>)",
// partial function application is rewritten to a closure
"replace(?, random:uuid(), random:uuid(), random:uuid())"
};
for(final String query : queries) {
String first = null;
for(int r = 0; r < 100; r++) {
try(QueryProcessor qp = new QueryProcessor(query, context)) {
qp.optimize();
final String plan = qp.toXml().serialize().toString();
if(first == null) first = plan;
else assertEquals(first, plan, "Run " + r + ": " + query);
}
}
}
}
}
Loading