Bug report
Bug description:
The documentation for annotate functions only requires that they implement format.VALUE.
However if a user created annotate function does not implement the other formats, it will likely behave badly when accessed through call_annotate_function using STRING or FORWARDREF formats.
Example:
from annotationlib import call_annotate_function, Format
def annotate(format, /):
if format == Format.VALUE:
print("pass")
return {}
else:
print("fail")
raise NotImplementedError(format)
call_annotate_function(annotate, Format.STRING)
Output:
The "fail" is expected, as annotate(Format.STRING) is called first and is clearly not supported.
The empty dictionary is returned because annotate(Format.VALUE_WITH_FAKE_GLOBALS) is called, but in a fake globals context where the globals namespace is empty. Format.VALUE in this context becomes a _Stringifier, so format == Format.VALUE becomes a _Stringifier which evaluates to True. print is also a _Stringifier which is why "pass" is never printed.
Note that if you change the condition to check format == Format.VALUE_WITH_FAKE_GLOBALS first, the condition will pass (for the wrong reason) but the function will fail with a TypeError instead, because NotImplementedError is also a _Stringifier and exceptions must derive from BaseException.
Format.FORWARDREF should probably check for NotImplementedError on the first call where globals do exist and reraise in that case, I don't see a way of handling Format.STRING without an extra check before it calls the function in the fake globals environment.
CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
No response
Linked PRs
Bug report
Bug description:
The documentation for annotate functions only requires that they implement
format.VALUE.However if a user created
annotatefunction does not implement the other formats, it will likely behave badly when accessed throughcall_annotate_functionusingSTRINGorFORWARDREFformats.Example:
Output:
fail {}The "fail" is expected, as
annotate(Format.STRING)is called first and is clearly not supported.The empty dictionary is returned because
annotate(Format.VALUE_WITH_FAKE_GLOBALS)is called, but in a fake globals context where the globals namespace is empty.Format.VALUEin this context becomes a_Stringifier, soformat == Format.VALUEbecomes a_Stringifierwhich evaluates toTrue.printis also a_Stringifierwhich is why "pass" is never printed.Note that if you change the condition to check
format == Format.VALUE_WITH_FAKE_GLOBALSfirst, the condition will pass (for the wrong reason) but the function will fail with aTypeErrorinstead, becauseNotImplementedErroris also a_Stringifierand exceptions must derive fromBaseException.Format.FORWARDREFshould probably check forNotImplementedErroron the first call where globals do exist and reraise in that case, I don't see a way of handlingFormat.STRINGwithout an extra check before it calls the function in the fake globals environment.CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
No response
Linked PRs
VALUE_WITH_FAKE_GLOBALSformat is implemented before calling an__annotate__function with empty fake globals #138788call_annotate_functionfallback to usingVALUEannotations if both the requested format andVALUE_WITH_FAKE_GLOBALSare not implemented #138803call_annotate_functionfallback to usingVALUEannotations if both the requested format andVALUE_WITH_FAKE_GLOBALSare not implemented (GH-138803) #140426