two instances that have been built on a different reader will be considered equals
This is true, but there aren't really any circumstances under which it would happen. Comparison for equality is done when building queries or sorts, and these are indexreader independent. It's only after a rewrite that the values would actually be different, and the only callers of rewrite are internal lucene classes. It would be nice to enforce this somehow by making rewrite() protected, but unfortunately I think it's called from different packages/modules so this won't work.
Should we use == rather than equals
Did you remove the if (boost == 1f) return expl; from FunctionScoreQuery on purpose?
Yes; just returning the straight DVS explanation breaks things when the overall query matches, but the value source has no value - we end up returning Explanation.noMatch() from a document that matches, but has a zero score.
re TermFreqDoubleValuesSource that's a nice idea, I'll add a DoubleValues.EMPTY instance and use that.
rewriting only once is enough
Yes! Perhaps I should add some javadoc making that explicit. I really dislike the 'rewrite until things stay the same' API on Query...