diff --git a/basex-core/src/main/java/org/basex/query/func/Functions.java b/basex-core/src/main/java/org/basex/query/func/Functions.java index fb21a7cb11..ea899ede6e 100644 --- a/basex-core/src/main/java/org/basex/query/func/Functions.java +++ b/basex-core/src/main/java/org/basex/query/func/Functions.java @@ -179,7 +179,7 @@ public static Expr item(final QNm qnm, final int arity, final boolean runtime, } // user-defined function - final StaticFunc sf = qc.functions.get(info.sc(), name, arity); + final StaticFunc sf = qc.functions.get(info.sc(), name, arity, runtime); if(sf != null) { final Expr func = item(sf, fb, qc); if(sf.updating) qc.updating(); @@ -215,7 +215,7 @@ private static QNm funcName(final QNm name, final int arity, final InputInfo inf final QueryContext qc) { final StaticContext sc = info.sc(); - if(name.hasURI() || qc.functions.get(sc, name, arity) != null) return name; + if(name.hasURI() || qc.functions.get(sc, name, arity, false) != null) return name; return new QNm(name.local(), sc.funcNS != null ? sc.funcNS : FN_URI); } @@ -326,7 +326,7 @@ private static StaticFuncCall staticCall(final QNm name, final FuncBuilder fb, } final StaticFuncCall call = new StaticFuncCall(name, fb.args(), fb.keywords, fb.info); - qc.functions.setFunc(call, qc); + qc.functions.setFunc(call, fb.runtime, qc); return call; } diff --git a/basex-core/src/main/java/org/basex/query/func/StaticFuncs.java b/basex-core/src/main/java/org/basex/query/func/StaticFuncs.java index 7200b45a91..8254f927ea 100644 --- a/basex-core/src/main/java/org/basex/query/func/StaticFuncs.java +++ b/basex-core/src/main/java/org/basex/query/func/StaticFuncs.java @@ -56,7 +56,7 @@ public StaticFunc declare(final StaticContext sc, final QNm name, final Params p final byte[] modUri = Token.eq(name.uri(), FN_URI) ? FN_URI : QNm.uri(sc.module); final StaticFunc sf = new StaticFunc(name, params, expr, anns, vs, info, doc); - if(get(sc, name, sf.min, sf.arity()) != null) throw DUPLFUNC_X.get(info, name); + if(get(sc, name, sf.min, sf.arity(), false) != null) throw DUPLFUNC_X.get(info, name); funcsByModule.computeIfAbsent(modUri, QNmMap::new).computeIfAbsent(name, ArrayList::new). add(sf); return sf; @@ -76,14 +76,16 @@ public Expr newRef(final QuerySupplier resolve) { /** * Assigns a function to a static function call. * @param call name function name + * @param useDynamicContext {@code true} if function lookup should include the dynamic context * @param qc query context * @throws QueryException query exception */ - void setFunc(final StaticFuncCall call, final QueryContext qc) throws QueryException { + void setFunc(final StaticFuncCall call, final boolean useDynamicContext, final QueryContext qc) + throws QueryException { final InputInfo info = call.info(); final QNm name = call.name; final int arity = call.arity(); - final StaticFunc func = get(info.sc(), name, arity); + final StaticFunc func = get(info.sc(), name, arity, useDynamicContext); if(func != null) { if(func.expr == null) throw FUNCNOIMPL_X.get(func.info, func.name.prefixString()); call.setFunc(func); @@ -128,10 +130,12 @@ public void compileAll(final CompileContext cc) { * @param sc static context * @param qname function name * @param arity function arity + * @param useDynamicContext {@code true} if function lookup should include the dynamic context * @return function if found, {@code null} otherwise */ - public StaticFunc get(final StaticContext sc, final QNm qname, final int arity) { - return get(sc, qname, arity, arity); + public StaticFunc get(final StaticContext sc, final QNm qname, final int arity, + final boolean useDynamicContext) { + return get(sc, qname, arity, arity, useDynamicContext); } /** @@ -140,13 +144,15 @@ public StaticFunc get(final StaticContext sc, final QNm qname, final int arity) * @param qname function name * @param min minimum function arity * @param max maximum function arity + * @param useDynamicContext {@code true} if function lookup should include the dynamic context * @return function if found, {@code null} otherwise */ - private StaticFunc get(final StaticContext sc, final QNm qname, final int min, final int max) { + private StaticFunc get(final StaticContext sc, final QNm qname, final int min, final int max, + final boolean useDynamicContext) { final byte[] funcUri = qname.uri(); final byte[] modUri = Token.eq(funcUri, FN_URI) ? FN_URI : QNm.uri(sc.module); StaticFunc func = get(modUri, qname, min, max); - if(func == null && sc.imports.contains(funcUri)) { + if(func == null && (useDynamicContext || sc.imports.contains(funcUri))) { func = get(funcUri, qname, min, max); if(func != null && func.anns.contains(Annotation.PRIVATE)) func = null; } diff --git a/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunction.java b/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunction.java index 17275e7330..1831c8573f 100644 --- a/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunction.java +++ b/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunction.java @@ -1,7 +1,6 @@ package org.basex.query.func.inspect; import org.basex.query.*; -import org.basex.query.ann.*; import org.basex.query.func.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -22,16 +21,7 @@ public FNode item(final QueryContext qc, final InputInfo ii) throws QueryExcepti StaticFunc func = null; if(name != null) { final int arity = function.arity(); - func = qc.functions.get(ii.sc(), name, arity); - if(func == null) { - for(final StaticFunc sf : qc.functions) { - if(!sf.annotations().contains(Annotation.PRIVATE) && sf.funcName().eq(name) - && sf.minArity() <= arity && sf.arity() >= arity) { - func = sf; - break; - } - } - } + func = qc.functions.get(ii.sc(), name, arity, true); } return new PlainDoc(qc, info).function(name, func, function.funcType(), function.annotations()); } diff --git a/basex-core/src/test/java/org/basex/query/ModuleTest.java b/basex-core/src/test/java/org/basex/query/ModuleTest.java index a2afd298ca..a0b5f5c52f 100644 --- a/basex-core/src/test/java/org/basex/query/ModuleTest.java +++ b/basex-core/src/test/java/org/basex/query/ModuleTest.java @@ -243,10 +243,10 @@ public final class ModuleTest extends SandboxTest { + "};\n" + "declare variable $c:hello := 'can you see me now';"); - // function is not visible to fn:function-lookup (not in dynamically known function definitions) + // function is visible to fn:function-lookup even when not in static context query("import module namespace b = 'b' at '" + b.path() + "';\n" - + "fn:function-lookup(#Q{c}hello, 0)", ""); - // function is still visible to inspect:functions + + "fn:function-lookup(#Q{c}hello, 0)()", "can you see me now"); + // function is visible to inspect:functions query("import module namespace b = 'b' at '" + b.path() + "';\n" + "inspect:functions()", "Q{c}hello#0"); @@ -260,4 +260,20 @@ public final class ModuleTest extends SandboxTest { + "declare namespace c = 'c';\n" + "$c:hello", QueryError.INVISIBLEVAR_X); } + + /** Tests fn:function-lookup from within a library module for a module imported elsewhere. */ + @Test public void gh2641() { + final IOFile sandbox = sandbox(); + final IOFile a = new IOFile(sandbox, "a.xqm"); + final IOFile b = new IOFile(sandbox, "b.xqm"); + write(a, "module namespace a = 'A';\n" + + "declare function a:lookup() {\n" + + " function-lookup(QName('B', 'test'), 0)\n" + + "};"); + write(b, "module namespace b = 'B';\n" + + "declare function b:test() {};"); + query("import module namespace a = 'A' at '" + a.path() + "';\n" + + "import module namespace b = 'B' at '" + b.path() + "';\n" + + "exists(a:lookup())", true); + } }