facebook / facebook/pyrefly

Quoted forward references incorrectly resolve to shadowing class members

Open
#4,266 2 comments 0 reactions 1 assignee Claimed by @asukaminato0721 View on GitHub
conformance needs-discussion stale typing-spec
Dominant language
Rust
Stars
7k
Forks
516
PR merge metrics
No merged PRs in 30d

Description

## Describe the Bug

A quoted forward reference in a class body resolves against a same-named class
member. The Python typing conformance test expects the quoted reference to
resolve outside the class scope, while the equivalent unquoted annotation
resolves to the class member and is rejected as an invalid type.

Minimal reproduction:

```python
from typing import assert_type

class ClassD:
def int(self) -> None:
...

x: "int" = 0 # OK: quoted reference resolves to builtins.int
y: int = 0 # E: local method `int` isn't a legal type expression

assert_type(ClassD.x, int)
```

The quoted annotation produces an error for `x`:

```text
Expected a type form, got instance of `(self: Self@ClassD) -> None`
```

It then treats `ClassD.x` as `Unknown`, producing a second failure:

```text
assert_type(Unknown, int) failed
```

Expected behavior:

- Accept the quoted annotation `"int"` and resolve it to `builtins.int`.
- Continue rejecting the unquoted annotation `int`, which refers to the local
method and isn't a valid type expression.
- Preserve `ClassD.x` as `int` without the cascading `Unknown` error.

The mismatch appears in
[`conformance.result`](https://github.com/facebook/pyrefly/blob/main/conformance/third_party/conformance.result#L11-L15).
The corresponding test is
[`annotations_forward_refs.py`](https://github.com/facebook/pyrefly/blob/main/conformance/third_party/annotations_forward_refs.py#L77-L96).

## Related Work

- [Issue #1900](https://github.com/facebook/pyrefly/issues/1900) discusses
same-named methods shadowing types, but its reproduction uses unquoted
annotations. Its discussion points to the expected error for the unquoted
case in this conformance test.
- [PR #1983](https://github.com/facebook/pyrefly/pull/1983) added an
overload-specific lookup exception.
- [Issue #357](https://github.com/facebook/pyrefly/issues/357) and
[PR #3617](https://github.com/facebook/pyrefly/pull/3617) address unquoted
forward references evaluated before Python 3.14.
- [Issue #3387](https://github.com/facebook/pyrefly/issues/3387) and
[PR #3391](https://github.com/facebook/pyrefly/pull/3391) address a different
`from __future__ import annotations` case.

This issue specifically covers lookup semantics for a quoted annotation when a
class member shadows the referenced name.

## Sandbox Link

Not provided; the reproduction above is self-contained and comes directly from
the typing conformance suite.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.