Fix negative narrowing for isinstance tuple targets#3415
Conversation
Fixes facebook#3412. For `isinstance(x, (A, B))`, pyrefly previously computed the negative branch by subtracting each tuple element from the input type independently and then intersecting the results. When the input contained a type that overlapped with multiple tuple elements (e.g. `Iterable[Any]` is iterable and is therefore a supertype of both `str` and `bytes`), the intersection step would reintroduce the subtracted alternatives, so a: float | str | bytes | Iterable[Any] if isinstance(a, (str, bytes)): ... else: reveal_type(a) # was Iterable[Any] | bytes | float | str came out unchanged. The fix walks the input union once and subtracts every tuple element from each member in turn, so members that ARE in the tuple drop out and members that aren't (`float`, `Iterable[Any]`) are kept. Adds a regression test in `pyrefly/lib/test/narrow.rs`.
b5371c8 to
2a86c1b
Compare
|
Diff from mypy_primer, showing the effect of this PR on open source code: bokeh (https://github.com/bokeh/bokeh)
- ERROR src/bokeh/plotting/contour.py:308:31-36: Argument `Sequence[ColorLike] | str | tuple[int, int, int] | tuple[int, int, int, float] | tuple[str, ...]` is not assignable to parameter `palette` with type `tuple[str, ...]` in function `bokeh.palettes.interp_palette` [bad-argument-type]
+ ERROR src/bokeh/plotting/contour.py:308:31-36: Argument `Sequence[ColorLike] | tuple[int, int, int] | tuple[int, int, int, float] | tuple[str, ...]` is not assignable to parameter `palette` with type `tuple[str, ...]` in function `bokeh.palettes.interp_palette` [bad-argument-type]
streamlit (https://github.com/streamlit/streamlit)
- ERROR lib/streamlit/elements/media.py:488:28-40: Object of class `RawIOBase` has no attribute `tobytes`
- Object of class `str` has no attribute `tobytes` [missing-attribute]
pandera (https://github.com/pandera-dev/pandera)
- ERROR pandera/engines/geopandas_engine.py:151:29-58: Cannot index into `Series` [bad-index]
ibis (https://github.com/ibis-project/ibis)
- ERROR ibis/backends/polars/__init__.py:298:54-58: Argument `Iterable[Path | str] | str` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
+ ERROR ibis/backends/polars/__init__.py:298:54-58: Argument `Iterable[Path | str]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
- ERROR ibis/backends/polars/__init__.py:301:54-58: Argument `Iterable[Path | str] | str | Unknown` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
+ ERROR ibis/backends/polars/__init__.py:301:54-58: Argument `Iterable[Path | str] | Unknown` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
strawberry (https://github.com/strawberry-graphql/strawberry)
- ERROR strawberry/extensions/mask_errors.py:61:34-55: Object of class `ExecutionResult` has no attribute `initial_result` [missing-attribute]
static-frame (https://github.com/static-frame/static-frame)
- ERROR static_frame/core/util.py:2491:63-66: Type `bool` is not iterable [not-iterable]
- ERROR static_frame/core/util.py:2491:63-66: Type `date` is not iterable [not-iterable]
- ERROR static_frame/core/util.py:2491:63-66: Type `int` is not iterable [not-iterable]
- ERROR static_frame/core/util.py:2491:63-66: Type `integer` is not iterable [not-iterable]
werkzeug (https://github.com/pallets/werkzeug)
- ERROR src/werkzeug/datastructures/structures.py:42:23-33: Yielded type `tuple[tuple[K, V] | K, list[V] | tuple[V, ...] | V | Unknown]` is not assignable to declared yield type `tuple[K, V]` [invalid-yield]
+ ERROR src/werkzeug/datastructures/structures.py:42:23-33: Yielded type `tuple[tuple[K, V] | K, V | Unknown]` is not assignable to declared yield type `tuple[K, V]` [invalid-yield]
kornia (https://github.com/kornia/kornia)
- ERROR kornia/models/depth_estimation/base.py:62:35-45: Object of class `list` has no attribute `cpu` [missing-attribute]
- ERROR kornia/models/depth_estimation/base.py:63:46-59: Object of class `list` has no attribute `device` [missing-attribute]
- ERROR kornia/models/depth_estimation/base.py:63:67-79: Object of class `list` has no attribute `dtype` [missing-attribute]
- ERROR kornia/models/segmentation/base.py:183:40-59: Object of class `list` has no attribute `size` [missing-attribute]
scrapy (https://github.com/scrapy/scrapy)
- ERROR scrapy/http/headers.py:55:22-27: `Iterable[_RawValue] | bytes | int | str` is not assignable to variable `_value` with type `Iterable[_RawValue]` [bad-assignment]
+ ERROR scrapy/http/headers.py:55:22-27: `Iterable[_RawValue] | int` is not assignable to variable `_value` with type `Iterable[_RawValue]` [bad-assignment]
- ERROR scrapy/http/headers.py:57:22-29: `list[Iterable[_RawValue] | bytes | int | str]` is not assignable to variable `_value` with type `Iterable[_RawValue]` [bad-assignment]
+ ERROR scrapy/http/headers.py:57:22-29: `list[Iterable[_RawValue] | int]` is not assignable to variable `_value` with type `Iterable[_RawValue]` [bad-assignment]
spark (https://github.com/apache/spark)
- ERROR python/pyspark/sql/pandas/conversion.py:1028:59-70: Object of class `list` has no attribute `json` [missing-attribute]
- ERROR python/pyspark/testing/pandasutils.py:464:55-70: Object of class `Index` has no attribute `to_pandas` [missing-attribute]
|
Primer Diff Classification✅ 5 improvement(s) | ➖ 4 neutral | 9 project(s) total | +6, -15 errors 5 improvement(s) across pandera, strawberry, static-frame, kornia, spark.
Detailed analysis✅ Improvement (5)pandera (-1)
strawberry (-1)
static-frame (-1)
kornia (-4)
spark (-2)
➖ Neutral (4)bokeh (+1, -1)
ibis (+2, -2)
werkzeug (+1, -1)
scrapy (+2, -2)
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (4 heuristic, 5 LLM) |
|
@yangdanny97 has imported this pull request. If you are a Meta employee, you can view this in D105741367. |
stroxler
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
|
@yangdanny97 merged this pull request in 92413ec. |
Fixes #3412.
This fixes negative narrowing for
isinstancechecks where the target is a tuple of runtime classes, such asisinstance(x, (str, bytes)).Summary
isinstance(x, (A, B)),narrow_is_not_instancepreviously subtracted each tuple element from the full input type independently and then intersected the per-element results.Iterable[Any]is a supertype of bothstrandbytes), the intersection step would reintroduce the subtracted alternatives through subset-based intersection. Concretely,intersect_impl(Iterable[Any], str)returnsstrbecausestr <: Iterable[Any].type[C]variable) are skipped, preserving existing conservative behavior.Repro
Before:
Iterable[Any] | bytes | float | strAfter:
Iterable[Any] | floatTest plan
test_isinstance_tuple_negative_with_overlapinpyrefly/lib/test/narrow.rs.cargo test -p pyrefly --lib— 5152 passed, 0 failed.test_isinstance_*tests continue to pass, including the existingtest_isinstance_type_negative_partial_narrowwhich exercises the not-finaltype[C]skip path.target/debug/pyrefly.