Skip to content

Conversation

@sharkdp
Copy link
Contributor

@sharkdp sharkdp commented Aug 28, 2025

Summary

With this PR, we stop performing boundness analysis for implicit instance attributes:

class C:
    def __init__(self):
        if False:   
            self.x = 1

C().x  # would previously show an error, with this PR we pretend the attribute exists

This PR is potentially just a temporary measure until we find a better fix. But I have already invested a lot of time trying to find the root cause of astral-sh/ty#758 (and this example, which I'm not entirely sure is related) and I still don't understand what is going on. This PR fixes the performance problems in both of these problems (in a rather crude way).

The impact of the proposed change on the ecosystem is small, and the three new diagnostics are arguably true positives (previously hidden because we considered the code unreachable, based on e.g. assertions that depended on implicit instance attributes). So this seems like a reasonable fix for now.

Note that we still support cases like these:

class D:
    if False:  # or any other expression that statically evaluates to `False`
        x: int = 1

D().x  # still an error


class E:
    if False:  # or any other expression that statically evaluates to `False`
        def f(self):
            self.x = 1

E().x  # still an error

closes astral-sh/ty#758

Test Plan

Updated tests, benchmark results

@sharkdp sharkdp added the ty Multi-file analysis & type inference label Aug 28, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Aug 28, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@github-actions
Copy link
Contributor

github-actions bot commented Aug 28, 2025

mypy_primer results

Changes were detected when running on open source projects
trio (https://github.com/python-trio/trio)
+ src/trio/_tests/test_ssl.py:932:15: error[call-non-callable] Object of type `None` is not callable
- Found 675 diagnostics
+ Found 676 diagnostics

ignite (https://github.com/pytorch/ignite)
+ tests/ignite/metrics/test_precision.py:377:17: warning[possibly-unbound-attribute] Attribute `cpu` on type `Unknown | int | float | list[int | float] | list[str] | list[Any]` is possibly unbound
+ tests/ignite/metrics/test_recall.py:380:17: warning[possibly-unbound-attribute] Attribute `cpu` on type `Unknown | int | float | list[int | float] | list[str] | list[Any]` is possibly unbound
- Found 2107 diagnostics
+ Found 2109 diagnostics
No memory usage changes detected ✅

@sharkdp sharkdp force-pushed the david/no-reachability-for-implicit-attributes branch from 62b81ec to 33f6df6 Compare August 28, 2025 13:21
@sharkdp sharkdp changed the title [ty] No reachability analysis for implicit instance attributes [ty] No boundness analysis for implicit instance attributes Aug 28, 2025
sharkdp added a commit that referenced this pull request Aug 28, 2025
…20133)

## Summary

Add regression benchmarks for the problematic cases in
astral-sh/ty#758. I'd like to merge this
before #20128 to measure the
impact (local tests show that this will "solve" both cases).
@sharkdp sharkdp force-pushed the david/no-reachability-for-implicit-attributes branch from 33f6df6 to ba3c7e8 Compare August 28, 2025 13:26
}

fn analyze_single(db: &dyn Db, predicate: &Predicate) -> Truthiness {
let _span = tracing::trace_span!("analyze_single", ?predicate).entered();
Copy link
Contributor Author

@sharkdp sharkdp Aug 28, 2025

Choose a reason for hiding this comment

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

I have added and removed this so many times... it's obviously useful, so I guess there's no harm in leaving it.

@sharkdp sharkdp force-pushed the david/no-reachability-for-implicit-attributes branch from ba3c7e8 to ceab895 Compare August 28, 2025 13:42
@codspeed-hq
Copy link

codspeed-hq bot commented Aug 28, 2025

CodSpeed Instrumentation Performance Report

Merging #20128 will improve performances by ×3.9

Comparing david/no-reachability-for-implicit-attributes (ceab895) with main (e586f6d)

Summary

⚡ 2 improvements
✅ 41 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
ty_micro[complex_constrained_attributes_2] 96.3 ms 63 ms +52.86%
ty_micro[complex_constrained_attributes_3] 261 ms 66.8 ms ×3.9

@sharkdp sharkdp marked this pull request as ready for review August 28, 2025 13:59
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Branch-dependent attributes are much more common in stub files than in runtime code. I wonder if it would make sense to add a diagnostic that enforces that you should never have any implicit instance attributes in a stub file. I.e., since we will now have different behaviour for the two different snippets, we could add a diagnostic that says that in a stub file you should never do this:

import sys

class F:
    def __init__(self) -> None:
        if sys.version_info >= (3, 13):
            self.x: int

and that instead you should always do this:

import sys

class Foo:
    if sys.version_info >= (3, 13):
        x: int

(but the new diagnostic could probably trigger for any implicit instance attribute in a stub file, not just ones inside branches -- it'll be simpler to implement that way, and there's no real reason to ever have any implicit instance attributes in a stub file)

Then again, now that I think about it, that's all already covered by https://docs.astral.sh/ruff/rules/non-empty-stub-body/, so maybe we should just recommend that users should enable that Ruff rule on their stub files

@sharkdp
Copy link
Contributor Author

sharkdp commented Aug 28, 2025

Branch-dependent attributes are much more common in stub files than in runtime code. I wonder if it would make sense to add a diagnostic that enforces that you should never have any implicit instance attributes in a stub file. I.e., since we will now have different behaviour for the two different snippets […]

I don't see any reason why someone would declare the type of instance attributes the complicated way in a stub, if there is a much easier way to just declare it on the body of the class. The implicit syntax wouldn't enable any use cases that wouldn't be covered by a simple attribute: type declaration on the body, I think? So I would argue that this inconsistency is nothing we need to immediately worry about, especially if there is a Ruff lint that would "catch" it.

@sharkdp sharkdp merged commit b3c4005 into main Aug 28, 2025
38 checks passed
@sharkdp sharkdp deleted the david/no-reachability-for-implicit-attributes branch August 28, 2025 14:25
@AlexWaygood
Copy link
Member

I don't see any reason why someone would declare the type of instance attributes the complicated way in a stub, if there is a much easier way to just declare it on the body of the class.

there's no reason, no! But, folks do strange things sometimes -- I'd wager money that there's a stub file out there somewhere that does something like that 😆

dcreager added a commit that referenced this pull request Aug 28, 2025
* main:
  Fix mdtest ignore python code blocks (#20139)
  [ty] add support for cyclic legacy generic protocols (#20125)
  [ty] add cycle detection for find_legacy_typevars (#20124)
  Use new diff rendering format in tests (#20101)
  [ty] Fix 'too many cycle iterations' for unions of literals (#20137)
  [ty] No boundness analysis for implicit instance attributes (#20128)
  Bump 0.12.11 (#20136)
  [ty] Benchmarks for problematic implicit instance attributes cases (#20133)
  [`pyflakes`] Fix `allowed-unused-imports` matching for top-level modules (`F401`) (#20115)
  Move GitLab output rendering to `ruff_db` (#20117)
  [ty] Evaluate reachability of non-definitely-bound to Ambiguous (#19579)
  [ty] Introduce a representation for the top/bottom materialization of an invariant generic (#20076)
  [`flake8-async`] Implement `blocking-http-call-httpx` (`ASYNC212`) (#20091)
  [ty] print diagnostics with fully qualified name to disambiguate some cases (#19850)
  [`ruff`] Preserve relative whitespace in multi-line expressions (`RUF033`) (#19647)
second-ed pushed a commit to second-ed/ruff that referenced this pull request Sep 9, 2025
…stral-sh#20133)

## Summary

Add regression benchmarks for the problematic cases in
astral-sh/ty#758. I'd like to merge this
before astral-sh#20128 to measure the
impact (local tests show that this will "solve" both cases).
second-ed pushed a commit to second-ed/ruff that referenced this pull request Sep 9, 2025
…h#20128)

## Summary

With this PR, we stop performing boundness analysis for implicit
instance attributes:

```py
class C:
    def __init__(self):
        if False:   
            self.x = 1

C().x  # would previously show an error, with this PR we pretend the attribute exists
```

This PR is potentially just a temporary measure until we find a better
fix. But I have already invested a lot of time trying to find the root
cause of astral-sh/ty#758 (and [this
example](astral-sh/ty#758 (comment)),
which I'm not entirely sure is related) and I still don't understand
what is going on. This PR fixes the performance problems in both of
these problems (in a rather crude way).

The impact of the proposed change on the ecosystem is small, and the
three new diagnostics are arguably true positives (previously hidden
because we considered the code unreachable, based on e.g. `assert`ions
that depended on implicit instance attributes). So this seems like a
reasonable fix for now.

Note that we still support cases like these:

```py
class D:
    if False:  # or any other expression that statically evaluates to `False`
        x: int = 1

D().x  # still an error


class E:
    if False:  # or any other expression that statically evaluates to `False`
        def f(self):
            self.x = 1

E().x  # still an error
```

closes astral-sh/ty#758

## Test Plan

Updated tests, benchmark results
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slow execution for attribute assignment and access with reachability constraints

3 participants