interfaces: add the optional `bdiff.xdiffblocks()` method
PyCharm flagged where this was called on the protocol class in `mdiff.py` in the
previous commit, but pytype completely missed it. PyCharm is correct here, but
I'm committing this separately to highlight this potential problem- some of the
implementations don't implement _all_ of the methods the others do, and there's
not a great way to indicate on a protocol class that a method or attribute is
optional- that's kinda the opposite of what static typing is about.
Making the method an `Optional[Callable]` attribute works here, and keeps both
PyCharm and pytype happy, and the generated `mdiff.pyi` and `modules.pyi` look
reasonable. We might be getting a little lucky, because the method isn't
invoked directly- it is returned from another method that selects which block
function to use. Except since it is declared on the protocol class, every
module needs this attribute (in theory, but in practice this doesn't seem to be
checked), so the check for it on the module has to change from `hasattr()` to
`getattr(..., None)`. We defer defining the optional attrs to the type checking
phase as an extra precaution- that way it isn't an attr with a `None` value at
runtime if someone is still using `hasattr()`.
As to why pytype missed this, I have no clue. The generated `mdiff.pyi` even
has the global variable typed as `bdiff: intmod.BDiff`, so uses of it really
should comply with what is on the class, protocol class or not.
=== property cache ===
calllog: []
cached value (unfiltered): NOCACHE
= first access on unfiltered, should do a call
access: 0
calllog: [0]
cached value (unfiltered): 0
= second access on unfiltered, should not do call
access 0
calllog: [0]
cached value (unfiltered): 0
= first access on "visible" view, should do a call
cached value ("visible" view): NOCACHE
access: 7
calllog: [0, 7]
cached value (unfiltered): 0
cached value ("visible" view): 7
= second access on "visible view", should not do call
access: 7
calllog: [0, 7]
cached value (unfiltered): 0
cached value ("visible" view): 7
= no effect on other view
cached value ("immutable" view): NOCACHE
access: 9
calllog: [0, 7, 9]
cached value (unfiltered): 0
cached value ("visible" view): 7
cached value ("immutable" view): 9
=== unfiltered property cache ===
unficalllog: []
cached value (unfiltered): NOCACHE
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE
= first access on unfiltered, should do a call
access (unfiltered): 100
unficalllog: [100]
cached value (unfiltered): 100
= second access on unfiltered, should not do call
access (unfiltered): 100
unficalllog: [100]
cached value (unfiltered): 100
= access on view should use the unfiltered cache
access (unfiltered): 100
access ("visible" view): 100
access ("immutable" view): 100
unficalllog: [100]
cached value (unfiltered): 100
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE
= even if we clear the unfiltered cache
cached value (unfiltered): NOCACHE
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE
unficalllog: [100]
access ("visible" view): 100
unficalllog: [100, 100]
cached value (unfiltered): 100
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE
access ("immutable" view): 100
unficalllog: [100, 100]
cached value (unfiltered): 100
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE
access (unfiltered): 100
unficalllog: [100, 100]
cached value (unfiltered): 100
cached value ("visible" view): NOCACHE
cached value ("immutable" view): NOCACHE