Skip to content

Comments

[ty] Refactor if narrowing#18392

Closed
ericmarkmartin wants to merge 7 commits intoastral-sh:mainfrom
ericmarkmartin:refactor-if-narrowing
Closed

[ty] Refactor if narrowing#18392
ericmarkmartin wants to merge 7 commits intoastral-sh:mainfrom
ericmarkmartin:refactor-if-narrowing

Conversation

@ericmarkmartin
Copy link
Contributor

@ericmarkmartin ericmarkmartin commented May 30, 2025

Summary

Refactor the semantic index builder’s If statement analysis. This change also lets us detect more unreachable code paths.

Test Plan

  • Add new tests to unreachable.md
  • Update existing tests

@MichaReiser MichaReiser changed the title Refactor if narrowing [ty] Refactor if narrowing May 30, 2025
@MichaReiser MichaReiser added the ty Multi-file analysis & type inference label May 30, 2025
@carljm
Copy link
Contributor

carljm commented May 30, 2025

Looks like there are still some failing tests here? Let me know if any of these are things you'd like a second pair of eyes on how to address; otherwise I'll mark this as draft, go ahead and put it back up for review when CI is green.

@carljm carljm marked this pull request as draft May 30, 2025 22:18
@ericmarkmartin
Copy link
Contributor Author

ericmarkmartin commented May 31, 2025

Looks like there are still some failing tests here? Let me know if any of these are things you'd like a second pair of eyes on how to address; otherwise I'll mark this as draft, go ahead and put it back up for review when CI is green.

Looks like we're green now, but mypy primer seems unhappy (sorry if that means I shouldn't have opened it, but I'm not sure how to proceed with primer failures)

@ericmarkmartin ericmarkmartin marked this pull request as ready for review May 31, 2025 21:40
@sharkdp
Copy link
Contributor

sharkdp commented Jun 4, 2025

Thank you for working on this!

Looks like we're green now, but mypy primer seems unhappy

The mypy_primer run was failing due to a network error. I re-ran it now and it fails with

fatal[panic] Panicked at crates/ty_python_semantic/src/symbol.rs:927:21 when checking `/tmp/mypy_primer/projects/trio/src/trio/_core/__init__.py`: `internal error: entered unreachable code: If we have at least one declaration, the scope-start should not be definitely visible`

This might be related to astral-sh/ty#365 (comment). I am planning to fix that bug as soon as #18041 is merged (which makes significant changes to the semantic index builder, and has a much higher impact). I suggest that we wait with this PR until these two things have been resolved.

def _[T](x: A | B):
if type(x) is A[str]:
reveal_type(x) # revealed: (A[int] & A[Unknown]) | (B & A[Unknown])
# TODO: previous was revealed: (A[int] & A[Unknown]) | (B & A[Unknown])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the conclusion that this is unreachable code is actually correct -- type(x) will never return a GenericAlias at runtime, so type(x) is A[str] can never be true, only type(x) is A can be.

This test probably warrants some expansion, but it doesn't need to happen in this PR. We have an issue open for improving how we handle narrowing against generic types.

Comment on lines +177 to +178
# TODO: false positive due to unreachable, causing @overload to be typed at
# Never
Copy link
Contributor

Choose a reason for hiding this comment

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

We intend to generally silence diagnostics in unreachable code, which should take care of this

Comment on lines +1551 to +1515
let v_constraint = self.record_visibility_constraint(predicate);
self.visit_body(clause_body);
self.record_visibility_constraint_id(v_constraint);
Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed, it definitely looks weird that we have to record the visibility constraint both before and after. I'm going to take a closer look to try to understand why that is.

@carljm
Copy link
Contributor

carljm commented Jun 6, 2025

Not able to reproduce the mypy-primer error running this locally. Will push a rebase and see if it repros in CI.

@carljm carljm force-pushed the refactor-if-narrowing branch from d282919 to 4af0119 Compare June 6, 2025 02:11
@codspeed-hq
Copy link

codspeed-hq bot commented Jun 6, 2025

CodSpeed Performance Report

Merging #18392 will degrade performances by 4.14%

Comparing ericmarkmartin:refactor-if-narrowing (4af0119) with main (cb8246b)

Summary

❌ 1 regressions
✅ 33 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
ty_micro[many_string_assignments] 65.7 ms 68.6 ms -4.14%

@carljm
Copy link
Contributor

carljm commented Jun 6, 2025

Hmm, well, it definitely repros here. That's odd. This is what I'm running locally:

➜ uvx \
            --from="git+https://github.com/hauntsaninja/mypy_primer@01a7ca325f674433c58e02416a867178d1571128" \
            mypy_primer --repo /Users/carlmeyer/projects/ruff \
            --type-checker ty \
            --old main \
            --new refactor-if-narrowing \
            --project-selector "/(trio)\$" \
            --output diff \
            --debug

@carljm
Copy link
Contributor

carljm commented Jun 6, 2025

Ah, figured it out.

The failing file is src/trio/_core/__init__.py, which contains this code:

# Windows imports
if sys.platform == "win32":
    from ._run import (
        current_iocp,
        monitor_completion_key,
        readinto_overlapped,
        register_with_iocp,
        wait_overlapped,
        write_overlapped,
    )
# Kqueue imports
elif sys.platform != "linux" and sys.platform != "win32":
    from ._run import current_kqueue, monitor_kevent, wait_kevent

And the failure repros locally if I run with --python-platform="linux". Otherwise locally my platform defaults to darwin, and the issue doesn't appear.

Since the repro is an if, not a loop with break, it seems clear this is an issue introduced by this PR; it's not astral-sh/ty#365 (comment)

@carljm
Copy link
Contributor

carljm commented Jun 10, 2025

@sharkdp is now working on a larger refactor of reachability and visibility constraints, we should revisit this after that's done and see what is still needed/relevant here. Thanks for the work on this PR, it helped demonstrate the need for this refactor.

@sharkdp sharkdp mentioned this pull request Jun 11, 2025
4 tasks
@AlexWaygood AlexWaygood removed their request for review June 16, 2025 22:35
sharkdp added a commit that referenced this pull request Jun 17, 2025
## Summary



* Completely removes the concept of visibility constraints. Reachability
constraints are now used to model the static visibility of bindings and
declarations. Reachability constraints are *much* easier to reason about
/ work with, since they are applied at the beginning of a branch, and
not applied retroactively. Removing the duplication between visibility
and reachability constraints also leads to major code simplifications
[^1]. For an overview of how the new constraint system works, see the
updated doc comment in `reachability_constraints.rs`.
* Fixes a [control-flow modeling bug
(panic)](astral-sh/ty#365) involving `break`
statements in loops
* Fixes a [bug where](astral-sh/ty#624) where
`elif` branches would have wrong reachability constraints
* Fixes a [bug where](astral-sh/ty#648) code
after infinite loops would not be considered unreachble
* Fixes a panic on the `pywin32` ecosystem project, which we should be
able to move to `good.txt` once this has been merged.
* Removes some false positives in unreachable code because we infer
`Never` more often, due to the fact that reachability constraints now
apply retroactively to *all* active bindings, not just to bindings
inside a branch.
* As one example, this removes the `division-by-zero` diagnostic from
astral-sh/ty#443 because we now infer `Never`
for the divisor.
* Supersedes and includes similar test changes as
#18392


closes astral-sh/ty#365
closes astral-sh/ty#624
closes astral-sh/ty#642
closes astral-sh/ty#648

## Benchmarks

Benchmarks on black, pandas, and sympy showed that this is neither a
performance improvement, nor a regression.

## Test Plan

Regression tests for:
- [x] astral-sh/ty#365
- [x] astral-sh/ty#624
- [x] astral-sh/ty#642
- [x] astral-sh/ty#648

[^1]: I'm afraid this is something that @carljm advocated for since the
beginning, and I'm not sure anymore why we have never seriously tried
this before. So I suggest we do *not* attempt to do a historical deep
dive to find out exactly why this ever became so complicated, and just
enjoy the fact that we eventually arrived here.

---------

Co-authored-by: Carl Meyer <[email protected]>
@sharkdp
Copy link
Contributor

sharkdp commented Jun 17, 2025

I'm going to close this, now that #18621 has been merged. @ericmarkmartin thank you for your work here.

@sharkdp sharkdp closed this Jun 17, 2025
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.

4 participants