You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hover on property with setter during assignment shows nothing #5218
When sending the hover lsp command on a property with a setter that is being assigned to it doesn't show anything. When not assigning it correctly shows the type and similarly for non-property values it shows it
classA:
def__init__(self) ->None:
self._x: int|None=None@propertydefx(self) ->int|None:
returnself._x@x.setterdefx(self, value: int|None) ->None:
self._x=valuedefmeth(self) ->None:
self.x=1# ERR! Hover on `x` returns nothing ("no information available" in neovim). Should show `int | None`self.x# OK! Hover correctly show int | Noneself._x=1# OK! Hover on `_x` correctly shows Literal[1]a=A()
a.x=1# ERR! Hover on `x` returns nothing ("no information available" in neovim). Should show `int | None`a.x# OK! Hover correctly show int | Nonea._x=1# OK! Hover on `_x` correctly shows Literal[1]
I was hoping to help solve this issue, so I dug into the codebase to see how this is currently handled.
I noticed there's actually a test in write_target_type.rs with a comment explaining why it behaves this way right now:
"Writing through a property, a descriptor or __setattr__ runs a setter, which produces no type Pyrefly can attribute to the target, so the target reports nothing. In particular the getter is never consulted: reading p.p and d.d gives int, which must not leak into the store."
So returning None was an intentional decision at the time to make sure getter types wouldn't leak into write targets (and to avoid what tools like Pyright and ty do, where they attach the RHS value type to the attribute).
That said, showing nothing isn't a solution.
I was thinking about what we could do here. Whenever someone hovers over an attribute in an assignment, if it resolves to a property with a setter, we could look up the setter method and display its parameter type.
Here is how both symmetric and asymmetric property would look before and after:
# Example 1: Symmetric PropertyclassA:
def__init__(self) ->None:
self._x: int|None=None@propertydefx(self) ->int|None:
returnself._x@x.setterdefx(self, value: int|None) ->None:
self._x=valuea=A()
# Before: Hover on `x` returns None ("no information available")# After: Hover on `x` returns `(attribute) x: int | None`a.x=1# Example 2: Asymmetric PropertyclassB:
@propertydefitems(self) ->tuple[int, ...]:
returnself._items@items.setterdefitems(self, value: Iterable[int]) ->None:
self._items=tuple(value)
b=B()
# Before: Hover on `items` returns None# After: Hover on `items` returns `(attribute) x: Iterable[int]` (the setter's input contract)b.items= [1, 2]
This way, we provide useful hover information on assignments while ensuring we never leak the getter type or show the RHS literal.
Do you think this makes sense, or did you have a different behavior in mind? If this sounds good to you and the maintainers, I'd love to claim the issue and work on a PR.
I think for me as a user, what would help the most would be to show that this is a property where the setter and getter differs in type. Even when they don't differ I think it's nice to know that it has a custom setter, because it can be surprising that assigning to a variable actually runs a function.
From a quick read of the LSP spec on HoverRequest there's no restrictions on what you're allowed to show so I don't think there's anything blocking from showing the full information
The hover displays the setter's type first since that's what the assignment accepts, with the getter listed below for context.
Custom Descriptors (__get__ / __set__)
In write_target_type.rs (test_property_and_descriptor_target) and attr.rs, AttrSetter::DescriptorSetter is handled alongside PropertySetter and currently has the same None target behavior.
Since Pyrefly already has get_descriptor_setter_value() for extracting the __set__ value parameter, I can extend the implementation to cover custom descriptors in the same PR.
Activity
StefanBRas commentedon Oct 13, 2026
For reference ty shows the just assigned type, which I (and the submitter of this issue) finds incorrect
Aayushyaash commentedon Oct 14, 2026
Hi @StefanBRas,
I was hoping to help solve this issue, so I dug into the codebase to see how this is currently handled.
I noticed there's actually a test in
write_target_type.rswith a comment explaining why it behaves this way right now:So returning
Nonewas an intentional decision at the time to make sure getter types wouldn't leak into write targets (and to avoid what tools like Pyright andtydo, where they attach the RHS value type to the attribute).That said, showing nothing isn't a solution.
I was thinking about what we could do here. Whenever someone hovers over an attribute in an assignment, if it resolves to a property with a setter, we could look up the setter method and display its parameter type.
Here is how both symmetric and asymmetric property would look before and after:
This way, we provide useful hover information on assignments while ensuring we never leak the getter type or show the RHS literal.
Do you think this makes sense, or did you have a different behavior in mind? If this sounds good to you and the maintainers, I'd love to claim the issue and work on a PR.
StefanBRas commentedon Oct 15, 2026
I think for me as a user, what would help the most would be to show that this is a property where the setter and getter differs in type. Even when they don't differ I think it's nice to know that it has a custom setter, because it can be surprising that assigning to a variable actually runs a function.
From a quick read of the LSP spec on HoverRequest there's no restrictions on what you're allowed to show so I don't think there's anything blocking from showing the full information
rchen152 commentedon Nov 5, 2026
Maybe we could use the getter type when the property is symmetric and print the property's getter and setter when it's asymmetric.
Aayushyaash commentedon Nov 6, 2026
Showing both getter and setter types for asymmetric properties makes a lot of sense.
Here’s a quick before & after of what this would look like for reads vs writes:
1. Symmetric property (
getter == setter)val = a.x):(property) x: int | None(unchanged)a.x = 1):None(property) x: int | None2. Asymmetric property (
getter != setter)res = b.items):(property) items: tuple[int, ...](unchanged)b.items = [1, 2]):Before:
NoneAfter:
The hover displays the setter's type first since that's what the assignment accepts, with the getter listed below for context.
Custom Descriptors (
__get__/__set__)In
write_target_type.rs(test_property_and_descriptor_target) andattr.rs,AttrSetter::DescriptorSetteris handled alongsidePropertySetterand currently has the sameNonetarget behavior.Since Pyrefly already has
get_descriptor_setter_value()for extracting the__set__value parameter, I can extend the implementation to cover custom descriptors in the same PR.Aayushyaash commentedon Nov 6, 2026
I'll start working on this and open a PR. #claim
github-actions commentedon Nov 6, 2026
@Aayushyaash you've claimed this issue — it's now assigned to you. Thanks for picking it up! 🎉