Skip to content

gh-89687: fix get_type_hints with dataclasses __init__ generation#29158

Closed
sobolevn wants to merge 11 commits into
python:mainfrom
sobolevn:issue-45524
Closed

gh-89687: fix get_type_hints with dataclasses __init__ generation#29158
sobolevn wants to merge 11 commits into
python:mainfrom
sobolevn:issue-45524

Conversation

@sobolevn

@sobolevn sobolevn commented Oct 22, 2021

Copy link
Copy Markdown
Member

This is a rather brave idea to manipulate globals, but it looks like it works.
It can backfire is some corner cases, though. But, I am not able to think about any. Ideas? 🙂

https://bugs.python.org/issue45524

CC @AlexWaygood @aidan.b.clark

@AlexWaygood AlexWaygood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like a good fix -- the test script I linked to in the BPO issue passes fine (as do the tests you've added, obviously). Nice job!

Comment thread Lib/dataclasses.py Outdated
Comment thread Lib/test/test_typing.py Outdated
Comment on lines 3301 to 3335

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Bikeshedding point, but: would it be better to put these tests in test_dataclasses? For the similar issue with TypedDict, the new tests were put next to the other TypedDict tests. Moreover, given that this is a very specific issue with the way that the dataclasses module generates __init__ functions, it seems like more of a dataclasses bug than a get_type_hints bug.

@sobolevn sobolevn Oct 22, 2021

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm fine with any decision from maintainers 👍
Both cases make sense.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed!

Comment thread Lib/test/test_typing.py Outdated
@sobolevn

sobolevn commented Oct 22, 2021

Copy link
Copy Markdown
Member Author

I found a corner case: when I define a proxy module, say dataclass_textanno2.py, with this content:

from __future__ import annotations

import dataclasses
from test import dataclass_textanno  # We need to be sure that `Foo` is not in scope


class Custom:
    pass


@dataclasses.dataclass
class Child(dataclass_textanno.Bar):
    custom: Custom


@dataclasses.dataclass(init=False)
class WithFutureInit(Child):
    def __init__(self, foo: dataclass_textanno.Foo, custom: Custom) -> None:
        pass

Then, this test does not pass:

def test_dataclass_from_proxy_module(self):
        from test import dataclass_textanno, dataclass_textanno2
        from dataclasses import dataclass

        @dataclass
        class Default(dataclass_textanno2.Child):
            pass

       self.assertEqual(
                    get_type_hints(Defualt.__init__),
                    {'foo': dataclass_textanno.Foo, 'custom': dataclass_textanno2.Custom,  'return': type(None)},
                )

Output:

======================================================================
ERROR: test_dataclass_from_proxy_module (test.test_typing.GetTypeHintTests) (klass=<class 'test.test_typing.GetTypeHintTests.test_dataclass_from_proxy_module.<locals>.Default'>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/sobolev/Desktop/cpython/Lib/test/test_typing.py", line 3376, in test_dataclass_from_proxy_module
    get_type_hints(klass.__init__),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/sobolev/Desktop/cpython/Lib/typing.py", line 1852, in get_type_hints
    value = _eval_type(value, globalns, localns)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/sobolev/Desktop/cpython/Lib/typing.py", line 334, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/sobolev/Desktop/cpython/Lib/typing.py", line 700, in _evaluate
    eval(self.__forward_code__, globalns, localns),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
NameError: name 'Custom' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.044s

FAILED (errors=1)
test test_typing failed
test_typing failed (1 error)

== Tests result: FAILURE ==

1 test failed:
    test_typing
@sobolevn

Copy link
Copy Markdown
Member Author

Now it is even more wild. I collect globals from all other mro items especially for __init__ annotations.
This is literally one of the craziest things I've ever done 😆

Comment thread Lib/test/test_typing.py Outdated
Comment thread Lib/test/test_typing.py Outdated
@sobolevn

Copy link
Copy Markdown
Member Author

One more case: same names obviously get overriden by the current logic. I will try something else.

@AlexWaygood

Copy link
Copy Markdown
Member

One more case: same names obviously get overriden by the current logic. I will try something else.

Not sure I understand. What's a failing test case?

@sobolevn

sobolevn commented Oct 29, 2021

Copy link
Copy Markdown
Member Author

@AlexWaygood done! Here's the failing test case: https://github.com/python/cpython/pull/29158/files#diff-f0436842533448f8f02a19081d7c9cc8372b6685756caf32c83dfecebbee68e7R3189-R3219

And here's my new solution: https://github.com/python/cpython/pull/29158/files#diff-44ce2dc1c4922b2f5cf7631d8f86cc569a4c25eb003aaecdc2bc22eb9163d5f5R455-R468

I now manually resolve str types into ForwardRef if they are used for __init__.
This looks like the most logical and easy way to solve our problem.

@AlexWaygood

Copy link
Copy Markdown
Member

@AlexWaygood done! Here's the failing test case: https://github.com/python/cpython/pull/29158/files#diff-f0436842533448f8f02a19081d7c9cc8372b6685756caf32c83dfecebbee68e7R3189-R3219

And here's my new solution: https://github.com/python/cpython/pull/29158/files#diff-44ce2dc1c4922b2f5cf7631d8f86cc569a4c25eb003aaecdc2bc22eb9163d5f5R455-R468

I now manually resolve str types into ForwardRef if they are used for __init__. This looks like the most logical and easy way to solve our problem.

Ahh, I understand -- thanks!

Comment thread Lib/dataclasses.py Outdated
Comment thread Lib/test/dataclass_textanno2.py Outdated
Comment thread Lib/test/dataclass_textanno2.py Outdated
@sobolevn

Copy link
Copy Markdown
Member Author

Thanks! Done 👍

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Nov 29, 2021
@sobolevn

Copy link
Copy Markdown
Member Author

Rebased 🙂

Friendly ping to @ericvsmith and @Fidget-Spinner

@github-actions github-actions Bot removed the stale Stale PR or inactive for long period of time. label Nov 30, 2021
@sobolevn sobolevn closed this Aug 28, 2022
@sobolevn sobolevn reopened this Aug 28, 2022
@ericvsmith

Copy link
Copy Markdown
Member

I'm putting this off until the SC makes a decision on PEP 649.

@picnixz picnixz changed the title bpo-45524: fix get_type_hints with dataclasses __init__ generation Feb 24, 2025
@Tishka17

Copy link
Copy Markdown

PEP 649 is accepted and implemented in 3.14, so what should be done with this PR?

@sobolevn

Copy link
Copy Markdown
Member Author

I lost context of this, please recreate :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment