Skip to content

TSP: inferred return type dropped at definition-site requests (unannotated functions / methods / @property getters render -> Unknown) #4042

Description

@rchiodo

Summary

When Pyrefly serves a type over TSP for a definition-site request — a function def, a method def, or an @property getter — the function's inferred return type is omitted: the wire Function carries return_type = Unknown. The same function requested at a reference / call site correctly carries its inferred return type. As a result, hovering an unannotated @property renders ... -> Unknown (originally reported as Any on 1.1.1) instead of the inferred type.

No protocol change is required. The existing Function.return_type field already conveys inferred returns — reference sites prove it (see the table below). Only the definition-site conversion path fails to populate it.

User-visible effect

Reported downstream as microsoft/pylance-release#8096. Hovering an unannotated @property getter (e.g. requests.Response.ok, apparent_encoding):

  • Expected (Pylance built-in Pyright): (property) ok: (self: Self@Response) -> bool
  • Actual (Pyrefly over TSP): (property) ok: (self: Self@Response) -> Unknown

Evidence (Pyrefly 1.2.0-dev.1 as the TSP server)

Hovering these constructs and observing the type Pyrefly sends over TSP:

Hover target Pyrefly TSP output
plain f referencedef f(): return True then a bare f (function) def f() -> Literal[True]
plain def f definition (function) f: Unknown
def m(self) method definition (method) def m(self: Self@C) -> Unknown
@property def ok(self) getter (property) ok: (self: Self@C) -> Unknown

Pyrefly's own checker infers all of these correctly, so the type is available and is lost only in the definition-site TSP conversion:

reveal_type(c.ok)                # revealed type: Literal[True]
reveal_type(c.apparent_encoding) # revealed type: Literal['utf-8']

Why this confirms no protocol change is needed

  • The protocol already models function return types: crates/tsp_types/src/protocol.rsFunctionType.return_type: Option<Box<Type>>.
  • Pyrefly already populates it: pyrefly/lib/tsp/type_conversion.rsconvert_function(...) sets return_type: Some(Box::new(ret)).
  • The reference-site row above shows this working end-to-end (-> Literal[True]), using the current protocol only.

The gap is specifically the definition-site path. In pyrefly/lib/state/lsp.rs, get_type_at_impl_with_options resolves a use-site via Key::BoundName (the fully-solved function whose .ret is inferred), but resolves a definition-site (FunctionDef / MethodDef identifier contexts) via Key::Definitionget_type_for_surfaceget_type_at(idx), which yields the function type before return-type inference is reflected. The @property getter surfaces the same definition-site behavior, which is what the downstream hover renders.

Suggested fix

When resolving a definition-site type for a function / method / property-getter, produce the same solved type the reference-site path yields (i.e. ensure the inferred return type is computed and attached) before converting to the TSP Function. No changes to tsp_types or the wire protocol are needed.

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    language-serverIssues specific to our IDE integration rather than type checking

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions