python-attrs / python-attrs/attrs

Incorrect annotation for forward reference in generated constructor

Open
#1,596 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.8k
Forks
480
Avg merge
2h 15m
Merged PRs (30d)
2

Description

When a class is decorated with @define or @frozen, and an attribute references a type declared later in the file, the generated __init__ method contains a type annotation that is different from the one on a hand-written __init__ method.

In Python 3.14 without from __future__ import annotations:

import inspect

from attrs import define


class Works:
    def __init__(self, foo: Foo) -> None:
        self._foo = foo


@define
class DoesNotWork:
    _foo: Foo


class Foo:
    pass


# prints `(foo: __main__.Foo) -> None`
print(inspect.signature(Works))

# prints `(foo: Foo) -> None` (a ForwardRef)
print(inspect.signature(DoesNotWork))

With from __future__ import annotations (tested on both 3.14 and 3.13 - same result):

from __future__ import annotations

import inspect

from attrs import define


class Works:
    def __init__(self, foo: Foo) -> None:
        self._foo = foo


@define
class DoesNotWork:
    _foo: Foo


class Foo:
    pass


# prints `(foo: __main__.Foo) -> None`
print(inspect.signature(Works, eval_str=True))

# crashes with `NameError: name 'Foo' is not defined`
print(inspect.signature(DoesNotWork, eval_str=True))

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the discrepancy with the two examples in the issue, comparing inspect.signature for handwritten and generated constructors on Python 3.13 and 3.14. Trace attrs' generated init annotation handling; done means the generated signature matches the handwritten one and the eval_str case no longer raises NameError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
developer-experience
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.