Skip to content

Commit 43f21ab

Browse files
joewizclaude
andcommitted
[refactor] Reduce complexity of XQ4 coercion checks flagged by Codacy
Simplify DynamicTypeCheck.isXQ4ImplicitCast to a single boolean return, dropping the xs:double/xs:float to xs:decimal cases already subsumed by the any-numeric to any-numeric rule; collapse the boolean return in isXQ4Relabeling; and extract the XQuery 4.0 coercion disjunct in Function.checkArgumentType into an isXQ4CoercionAllowed helper so it no longer inflates that method's condition. No behavior change; the full XQuery3Tests suite (including the XQ4 type-promotion tests) still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 50130c3 commit 43f21ab

2 files changed

Lines changed: 25 additions & 32 deletions

File tree

exist-core/src/main/java/org/exist/xquery/DynamicTypeCheck.java

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -215,31 +215,16 @@ private Item xq4ImplicitCast(Item item, final int type, final int requiredType)
215215
* The "to" column must match R exactly (the required type must be the primitive type).
216216
*/
217217
static boolean isXQ4ImplicitCast(final int sourceType, final int requiredType) {
218-
// xs:string → xs:anyURI
219-
if (Type.subTypeOf(sourceType, Type.STRING) && requiredType == Type.ANY_URI) {
220-
return true;
221-
}
222-
// xs:hexBinary ↔ xs:base64Binary
223-
if (Type.subTypeOf(sourceType, Type.HEX_BINARY) && requiredType == Type.BASE64_BINARY) {
224-
return true;
225-
}
226-
if (Type.subTypeOf(sourceType, Type.BASE64_BINARY) && requiredType == Type.HEX_BINARY) {
227-
return true;
228-
}
229-
// Bidirectional numeric: xs:double → xs:decimal, xs:float → xs:decimal
230-
// (Note: decimal→float, decimal→double, float→double already handled by XQ 3.1 rules)
231-
if (Type.subTypeOf(sourceType, Type.DOUBLE) && requiredType == Type.DECIMAL) {
232-
return true;
233-
}
234-
if (Type.subTypeOf(sourceType, Type.FLOAT) && requiredType == Type.DECIMAL) {
235-
return true;
236-
}
237-
// XQ4 also allows any numeric → any other numeric
238-
// "any numeric type to be implicitly converted to any other"
239-
if (Type.subTypeOfUnion(sourceType, Type.NUMERIC) && Type.subTypeOfUnion(requiredType, Type.NUMERIC)) {
240-
return true;
241-
}
242-
return false;
218+
return
219+
// xs:string → xs:anyURI
220+
(Type.subTypeOf(sourceType, Type.STRING) && requiredType == Type.ANY_URI)
221+
// xs:hexBinary ↔ xs:base64Binary
222+
|| (Type.subTypeOf(sourceType, Type.HEX_BINARY) && requiredType == Type.BASE64_BINARY)
223+
|| (Type.subTypeOf(sourceType, Type.BASE64_BINARY) && requiredType == Type.HEX_BINARY)
224+
// any numeric type → any other numeric type. This subsumes the specific cases such as
225+
// xs:double → xs:decimal and xs:float → xs:decimal; decimal→float, decimal→double and
226+
// float→double are already permitted by the XQuery 3.1 numeric type promotion rules.
227+
|| (Type.subTypeOfUnion(sourceType, Type.NUMERIC) && Type.subTypeOfUnion(requiredType, Type.NUMERIC));
243228
}
244229

245230
/**
@@ -255,12 +240,9 @@ static boolean isXQ4Relabeling(final int sourceType, final int requiredType) {
255240
}
256241
try {
257242
final int requiredPrimitive = Type.primitiveTypeOf(requiredType);
258-
// Relabeling only applies when R is a derived type (not a primitive itself)
259-
if (requiredPrimitive == requiredType) {
260-
return false;
261-
}
262-
// J must be an instance of the same primitive type P
263-
return Type.subTypeOf(sourceType, requiredPrimitive);
243+
// Relabeling only applies when R is a derived type (not a primitive itself),
244+
// and J must be an instance of the same primitive type P.
245+
return requiredPrimitive != requiredType && Type.subTypeOf(sourceType, requiredPrimitive);
264246
} catch (final IllegalArgumentException e) {
265247
return false;
266248
}

exist-core/src/main/java/org/exist/xquery/Function.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ private Expression checkArgumentType(
309309
//Because () is seen as a node
310310
(argType.getCardinality().isSuperCardinalityOrEqualOf(Cardinality.EMPTY_SEQUENCE) && returnType == Type.NODE) ||
311311
// XQuery 4.0: allow implicit casts and relabeling
312-
(context.getXQueryVersion() >= 40 && (DynamicTypeCheck.isXQ4ImplicitCast(returnType, argType.getPrimaryType()) || DynamicTypeCheck.isXQ4Relabeling(returnType, argType.getPrimaryType()))))) {
312+
isXQ4CoercionAllowed(returnType, argType.getPrimaryType()))) {
313313
LOG.debug(ExpressionDumper.dump(argument));
314314
throw new XPathException(this, ErrorCodes.XPTY0004, Messages.getMessage(Error.FUNC_PARAM_TYPE_STATIC,
315315
String.valueOf(argPosition), mySignature, argType.toString(), Type.getTypeName(returnType)));
@@ -330,6 +330,17 @@ private Expression checkArgumentType(
330330
return new DynamicTypeCheck(context, argType.getPrimaryType(), argument);
331331
}
332332

333+
/**
334+
* Whether a value of {@code sourceType} may be implicitly coerced to {@code requiredType}
335+
* under the XQuery 4.0 function coercion rules (spec §3.4.1): implicit casting or relabeling.
336+
* Always false below XQuery 4.0.
337+
*/
338+
private boolean isXQ4CoercionAllowed(final int sourceType, final int requiredType) {
339+
return context.getXQueryVersion() >= 40
340+
&& (DynamicTypeCheck.isXQ4ImplicitCast(sourceType, requiredType)
341+
|| DynamicTypeCheck.isXQ4Relabeling(sourceType, requiredType));
342+
}
343+
333344
protected boolean checkArgumentTypeCardinality(
334345
final Expression argument,
335346
@Nullable final SequenceType argType,

0 commit comments

Comments
 (0)