Teach `ghostwriter.equivalent` to handle positional-only params by position, not name
Consider the following example:
import math
from hypothesis.extra.ghostwriter import equivalent
def gcd(a: int, b: int) -> int:
return math.gcd(a, b)
print(equivalent(gcd, math.gcd))
@given(a=st.integers(), b=st.integers(), x=st.nothing(), y=st.nothing())
def test_equivalent_gcd_gcd(a, b, x, y):
result_0_gcd = test_expected_output.gcd(a=a, b=b)
result_1_gcd = math.gcd(x, y)
assert result_0_gcd == result_1_gcd, (result_0_gcd, result_1_gcd)
This is kinda silly: the Ghostwriter treats math.gcd(x, y, /) as if the argument names are meaningful! We wanted:
@given(a=st.integers(), b=st.integers())
def test_equivalent_gcd_gcd(a, b):
result_0_gcd = test_expected_output.gcd(a=a, b=b)
result_1_gcd = math.gcd(a, b)
assert result_0_gcd == result_1_gcd, (result_0_gcd, result_1_gcd)
The solution is going to involve expanding _make_equiv_body. Rough notes: get given_args in _make_equiv_body; use write_call with passed variable names for posonly arguments; prefer pos-or-kw names then kwonly names then pos-only names as successive fallbacks. very rough start: https://github.com/HypothesisWorks/hypothesis/compare/master...Zac-HD:hypothesis:posonly-eqiv-ghostwriter
Other ideas: sort equivalent funcs by qualname, for stable output under shouldn't-matter changes of ordering; use module name instead of index to distinguish function results; add a did-you-mean if one func is actually a module (or look up matching functions on it? also works for two modules...).