Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix trivial inference tests for Python 3.12 support #31170

Merged
merged 7 commits into from
May 9, 2024

Conversation

jrmccluskey
Copy link
Contributor

@jrmccluskey jrmccluskey commented May 3, 2024

Implements new operations for trivial inference ahead of Python 3.12 support to fix existing unit tests. These operations can be seen listed at https://docs.python.org/3/library/dis.html

Not every operation has been implemented, but not every operation comes up in the context of us doing type introspection on functions. A few of these operations were actually introduced in 3.11 and weren't used in our test cases until 3.12!

fixes: #31047


Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Mention the appropriate issue in your description (for example: addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, comment fixes #<ISSUE NUMBER> instead.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

See the Contributor Guide for more tips on how to make review process smoother.

To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md

GitHub Actions Tests Status (on master branch)

Build python source distribution and wheels
Python tests
Java tests
Go tests

See CI.md for more information about GitHub Actions CI or the workflows README to see a list of phrases to trigger workflows.

@github-actions github-actions bot added the python label May 3, 2024
@tvalentyn
Copy link
Contributor

R: @tvalentyn

Copy link
Contributor

github-actions bot commented May 3, 2024

Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control

@@ -143,6 +143,8 @@ def copy(self):
return FrameState(self.f, self.vars, self.stack, self.kw_names)

def const_type(self, i):
print(self.co.co_consts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover / if debug ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, leftover from some checking. This weirdly broke a test in the snippets directory that relies on stdout, which is maybe bad test design but did a good job catching these

"""Defines the actions intrinsic bytecodes have on the frame.

Each function here corresponds to a bytecode documented in
https://docs.python.org/2/library/dis.html or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Py2 docs shouldn't be relevant anymore.

pass


int_one_ops = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int_one_ops = [
INT_ONE_OPS = tuple([

(to make it immutable and and to show it's a constant.

intrinsic_typevartuple,
intrinsic_subscript_generic,
intrinsic_typealias
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
]
])

pass


int_one_ops = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment what defines the order of ops in this sequence? How can we make sure that this list stays up to date with future Python versions potentially adding more intrinsic ops? I worry that the order might become skewed but tests we won't notice the skew.

Is it possible to add a unit test that runs on Python 3.12+ which would fail if INT_ONE_OPS doesn't match the list of intrinsic ops known to dis? If that's not easy, maybe a unit test that would just fail on Python 3.13 with an instruction to: revisit intrinsic_one_ops.INT_ONE_OPS, make necessary changes, and change the test to fail starting next Python minor version. Then, it will make use manually look at this list once in a while.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if the dis package provides a utility to get that list easily so we can check against it. Otherwise documenting why the order is what it is and adding some check for python versions might be the best way to gate it off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I can pull the table out from the module in a slightly-janky way but it does become testable. That way if ops change orders or new ones are added we can be aware of it.


Each function here corresponds to a bytecode documented in
https://docs.python.org/2/library/dis.html or
https://docs.python.org/3/library/dis.html. The first argument is a (mutable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
https://docs.python.org/3/library/dis.html. The first argument is a (mutable)
https://docs.python.org/3/library/dis.html . The first argument is a (mutable)

(to make it clickable)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done (did it on my branch instead of taking the suggestion)

elif isinstance(base, typehints.IndexableTypeConstraint):
out = base
else:
out = element_type(base)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding, what is the interpretation of
out = element_type(base)?

does this say: take a container, and return the type of individual element that lives in the container? Wouldn't a slice always return a container as opposed to individual elements?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm that's actually an implementation interpretation I hadn't thought of. The documentation for the op is here - https://docs.python.org/3/library/dis.html#opcode-BINARY_SLICE and I was adapting the BINARY_SUBSCR code (which is doing similar work with a single index arg instead of a slice.) I think most cases will be caught by the indexable type constraint (so the type of the container is what we return) but that else case might not be handling things correctly. I'll see if I can get a bit more of an understanding on this op code specifically.

sdks/python/apache_beam/typehints/opcodes.py Show resolved Hide resolved
@@ -432,6 +433,8 @@ def infer_return_type_func(f, input_types, debug=False, depth=0):
elif op in dis.haslocal:
print('(' + co.co_varnames[arg] + ')', end=' ')
elif op in dis.hascompare:
if (sys.version_info.major, sys.version_info.minor) >= (3, 12):
arg = arg >> 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for my knowledge, where does this come from? Perhaps there is a link you could add in a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is funny in that it was actually an undocumented change as best as I could tell. The comparison operations are similar to the intrinsic functions here in that the arg for a comparison op is an integer index into a list of operations that you index into. The output is always a boolean here anyway so we don't necessarily care what the operation itself is, but for debugging purposes we get the operation to print anyway. The problem was that these indices got bit shifted (a trick this package really likes to do for operations in different versions), so our print statement here was failing with an out-of-range index.

I'll try to add some comments around some of these changes since they're a little arbitrary, but for the most part it really just breaks down to "this argument's representation was changed in the package so we have to change it here for this version"

if debug:
print("Executing intrinsic one op", int_op.__name__.upper())
int_op(state, arg)
elif opname == 'CALL_INTRINSIC_2':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a comment sth like: all currently available binary intrinsic ops are not relevant for type-checking, and as i mentioned above, also have some safeguard to revisit this logic if we can detect that the list of intrinsic ops has changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to think I can remove the intrinsic 2 call block here for now so if there's a failure later we will get the clear "cannot handle operation x" failure. I think this was a holdover from when I was anticipating having to implement most of the new calls to get tests green.

@@ -30,7 +30,7 @@ class IntrinsicOneOpsTest(unittest.TestCase):
def testOperationsOrder(self):
if sys.version_info >= (3, 12):
dis_order = dis.__dict__['_intrinsic_1_descs']
beam_order = list()
beam_order = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use list comprehensions for a more Pythonic syntax. e.g.

beam_ops = [fn.__name_upper() for fn in intrinsic_one_ops.INT_ONE_OPS]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done



class IntrinsicOneOpsTest(unittest.TestCase):
def testOperationsOrder(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def testOperationsOrder(self):
def test_unary_intrinsic_ops_are_in_the_same_order_as_in_cpython(self):
(self):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@tvalentyn tvalentyn merged commit 287ed38 into apache:master May 9, 2024
72 of 73 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request]: Support Type Inference on Python 3.12
2 participants