Return false from matches() when the node-set argument selects nothing - #141
Merged
Merged
Conversation
starts-with, ends-with and contains all return false when their node-set argument selects no node (func.go:354, :381, :408). matches() returned "" instead, so a boolean function handed back a string. notFunc has no case for string and falls through to its default, so not(matches(//no-such, 'x')) answered false where the three siblings answer true. Idiomatically //a[not(matches(@href, '^https'))] dropped every <a> with no href. boolean(X) and not(not(X)) also disagreed for the same X. asBool maps "" to false, which is why a bare predicate looked right and this went unnoticed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
starts-with,ends-withandcontainsall returnfalsewhen their node-set argument selects no node (func.go:354,:381,:408).matches()atfunc.go:436returned"", so a boolean function handed back a string.notFunc(func.go:638) has nocase stringand falls through todefault: return false, so the string is swallowed rather than negated:In practice
//a[not(matches(@href, '^https'))]drops every<a>that has nohref, while thestarts-withspelling keeps them. A missing attribute reaches the same branch.It went unnoticed because
asBool(func.go:257) maps""tofalse, so a bare predicate like//*[matches(@nope,'x')]is corrected on the way out.not()is the caller that is not defensive.There is a second way to close this, and it is worth saying out loud: leaving the early return out and letting the regex run against
""also removes the string return, and is arguably closer tostring(empty node-set)being"". It differs observably -matches(//no-such, '^$')becomestruerather thanfalse. I went withfalsebecause it is what the three sibling functions already do, but the type bug is there under either, so say the word if you prefer the other.Verification
Base
487996fb. New case sits next toTest_func_matchesinxpath_function_test.go; no new file.func.gomd5-run Test_func_matches8c32ba3520ab162f1c4a2171b389a9a5Test_func_matches_empty_nodeset)fce2401a8e2c34dc9e76ec94f464dea0return trueinsteadf1d1a2f9698a37defb59a11fe24598b38c32ba3520ab162f1c4a2171b389a9a5The pre-existing
Test_func_matchespasses in every row, including pristine - it never reaches the nil-node branch, so nothing in the suite currently pins this either way. The over-correction is caught only by the new case; I am not claiming it breaks an existing test.go test ./...ok,go vet ./...silent,gofmt -lclean for both changed files.gofmt -l .does listfunc_go110.goandfunc_pre_go110.go, which are unformatted on487996fbas well and are untouched here.The same four-line
case query:block appears innormalizespaceFuncatfunc.go:463;""is right there, matchingsubstringFuncand the convention #136 established for the string functions. OnlymatchesFuncis changed.Written with AI assistance (Claude); the measurements above were run locally and I have reviewed the change.