fix str not assignable to LiteralString attribute #3057#3076
fix str not assignable to LiteralString attribute #3057#3076asukaminato0721 wants to merge 3 commits intofacebook:mainfrom
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR fixes the false-positive from #3057 by tracking multiple method-defined instance-attribute writes (including self.attr += ...) and widening the inferred field type across those writes, rather than “freezing” on the first assignment.
Changes:
- Record and retain all writes to method-defined
self.<attr>fields, including augmented assignments, and use them to widen the inferred field type. - Refactor augmented-assignment type computation into a reusable helper for both statement checking and method-field inference.
- Add a regression test covering
LiteralString-ish initialization followed by+=widening tostr.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pyrefly/lib/test/literal.rs | Adds a regression test for self.attr += ... widening a previously-literal-string-like attribute to str. |
| pyrefly/lib/query.rs | Updates class-field type extraction to handle DefinedInMethod now carrying multiple writes. |
| pyrefly/lib/binding/stmt.rs | Records known self.<attr> augmented-assignment writes so later inference can widen across them. |
| pyrefly/lib/binding/scope.rs | Changes per-method instance-attribute tracking to store a list of writes; builds DefinedInMethod from those writes. |
| pyrefly/lib/binding/function.rs | Updates unchecked-body self-attr discovery to fit the new InstanceAttribute shape. |
| pyrefly/lib/binding/binding.rs | Updates ClassFieldDefinition::DefinedInMethod to hold multiple writes (MethodFieldWrites). |
| pyrefly/lib/alt/operators.rs | Extracts augmented-assignment result-type computation into augassign_result_type. |
| pyrefly/lib/alt/class/class_field.rs | Uses the collected method writes to widen the inferred field type across assignments/augassign. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .map(|(n, r)| { | ||
| ( | ||
| n, | ||
| InstanceAttribute( | ||
| ExprOrBinding::Binding(Binding::Any(AnyStyle::Implicit)), | ||
| None, | ||
| r, | ||
| MethodSelfKind::Instance, | ||
| ), | ||
| InstanceAttribute { | ||
| writes: vec![ExprOrBinding::Binding(Binding::Any(AnyStyle::Implicit))], | ||
| annotation: None, | ||
| range: r, | ||
| receiver_kind: MethodSelfKind::Instance, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
SelfAttrNames::find populates InstanceAttribute.receiver_kind with MethodSelfKind::Instance unconditionally. In the unchecked-body path this can misclassify cls.attr = ... writes in @classmethods (or other non-instance receivers) as instance attributes, which can affect downstream field initialization/visibility logic. Consider passing method_self_kind into SelfAttrNames::find (and storing it on the visitor) so receiver_kind matches the actual method receiver kind.
rchen152
left a comment
There was a problem hiding this comment.
This seems to be going in the same direction as #2356, which Danny wasn't able to merge due to mypy_primer regressions. That suggests to me that collecting all writes to infer a type is likely the wrong approach. What happens if you just try promoting LiteralString to str when inferring attribute types? We might need special-casing for ALL_CAPS constants, which we already have elsewhere to avoid promoting constant module attributes.
|
Diff from mypy_primer, showing the effect of this PR on open source code: urllib3 (https://github.com/urllib3/urllib3)
+ ERROR src/urllib3/response.py:644:16-42: Expected a type form, got instance of `tuple[*tuple[type[Exception], ...], Unknown, Unknown]` [not-a-type]
scikit-learn (https://github.com/scikit-learn/scikit-learn)
- ERROR sklearn/preprocessing/_target_encoder.py:488:33-49: `str | Unknown` is not assignable to attribute `target_type_` with type `LiteralString` [bad-assignment]
apprise (https://github.com/caronc/apprise)
- ERROR tests/helpers/rest.py:88:21-93:10: `str` is not assignable to attribute `body` with type `LiteralString` [bad-assignment]
starlette (https://github.com/encode/starlette)
- ERROR starlette/middleware/sessions.py:37:13-32: `str` is not assignable to attribute `security_flags` with type `LiteralString` [bad-assignment]
scrapy (https://github.com/scrapy/scrapy)
+ ERROR scrapy/utils/signal.py:61:16-24: Expected a type form, got instance of `tuple[*tuple[Any, ...], type[StopDownload]]` [not-a-type]
|
Primer Diff Classification❌ 2 regression(s) | ✅ 3 improvement(s) | 5 project(s) total | +2, -3 errors 2 regression(s) across urllib3, scrapy. error kinds:
Detailed analysis❌ Regression (2)urllib3 (+1)
scrapy (+1)
✅ Improvement (3)scikit-learn (-1)
apprise (-1)
starlette (-1)
Suggested fixesSummary: The refactoring of augmented assignment into augassign_result_type() changed tuple concatenation behavior: the old code checked the full (non-distributed) base type for being a tuple, while the new code checks each union member, causing incorrect tuple concat results when the base is a union containing a tuple type. 1. In
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (5 LLM) |
Summary
Fixes #3057
stops freezing a method-defined instance attribute at its first self.attr write and instead tracks later writes, including self.attr += ..., then widens the inferred field type across those writes.
Test Plan
add test