thanks again Gunther for reporting this bug. A fix has been applied (reference on bugzilla ID: 3464429 : https://sourceforge.net/tracker/?func=detail&aid=3464429&group_id=29872&atid=397617) which will appear in the next build of Saxon.

Thanks O'Neil. I've just been having another look at it myself (having retreated to the warmth of the cottage after an hour's bracing walk in freezing rain by the sea). I think your fix is sound but a little conservative: for example it tests for dependencies on both the variables declared in the for clause rather than just the position variable.

What I did was as follows:

(a) In FLWORExpression line 577, add "this" as an argument to ForClause.addPredicate(), so it becomes

boolean added = ((ForClause) clause).addPredicate(this, visitor, contextItemType, term);

(b) In ForClause.addPredicate(), change the method declaration to

public boolean addPredicate(FLWORExpression flwor, ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType, Expression condition) throws XPathException {
   
and add around the code that sets positionVariable to null:

    if (!ExpressionTool.dependsOnVariable(flwor, new Binding[]{positionVariable})) {
         positionVariable = null;
    }

(that's probably equivalent to your fix except that it only tests for dependencies on the positionVariable, not on both the variables).

(c) More of an enhancement than a big fix, I think we can extend the set of expressions that can be merged into a ForClause to include a CompareToIntegerConstant expression, by changing the code at ForClause line 234 to

if (positionVariable != null &&
                (term instanceof ValueComparison || term instanceof GeneralComparison || term instanceof CompareToIntegerConstant)) {
            ComparisonExpression comp = (ComparisonExpression) term;

and at line 257, casting predicate to ComparisonExpression rather than BinaryExpression.

With these changes the FLWORExpression

            for $e at $i in $c/*
            where $i > 1 and $i < count($c/*)
            return $e

optimizes down to

tail(subsequence($c/*, 1, count($c/*) - 1))

which feels good!

I've committed these changes replacing yours for the 9.5 branch; let's run some tests and if they work I think we can make the same changes for 9.4.

We're going up to Cheltenham tomorrow for Christmas Eve with my parents and various sisters, nephews, and neices, then back here for Christmas Day. We're having to do more self-catering than planned - the local pubs all seem to have given their cook the week off and aren't serving food!

Have a good Christmas,

Mike