Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/pyrefly_config/src/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ pub enum ErrorKind {
UnannotatedAttribute,
/// A function parameter is missing a type annotation.
UnannotatedParameter,
/// A protocol member is assigned a value in the class body without an explicit type annotation.
UnannotatedProtocolMember,
/// A function is missing a return type annotation.
UnannotatedReturn,
/// Attempting to use a name that may be unbound or uninitialized
Expand Down
14 changes: 14 additions & 0 deletions pyrefly/lib/alt/class/class_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,20 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
..
} => {
let direct_annotation = annot.map(|a| self.get_idx(a).annotation.clone());
if metadata.is_protocol()
&& direct_annotation.is_none()
&& !is_dunder(name.as_str())
{
self.error(
errors,
range,
ErrorInfo::Kind(ErrorKind::UnannotatedProtocolMember),
format!(
"Protocol member `{}` must have an explicit type annotation",
name,
),
);
}
let initialization = if let ExprOrBinding::Expr(e) = value.as_ref()
&& let Some(dm) = metadata.dataclass_metadata()
&& let Expr::Call(call) = e
Expand Down
11 changes: 6 additions & 5 deletions pyrefly/lib/test/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1008,15 +1008,16 @@ def to_foo() -> Foo[MySeries]:

// https://github.com/facebook/pyrefly/issues/2925
testcase!(
bug = "Should detect ambiguous protocol members with value assignments",
test_protocol_ambiguous_member,
r#"
from typing import Protocol

class Ambiguous(Protocol):
# Assigning a value in a Protocol body is ambiguous: is it declaring
# a member with a type, or providing a default value?
x = None
y = ...
x = None # E: Protocol member `x` must have an explicit type annotation
y = ... # E: Protocol member `y` must have an explicit type annotation

class Ok(Protocol):
x: int
y: str = "default"
"#,
);
15 changes: 15 additions & 0 deletions website/docs/error-kinds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,21 @@ def process_data(x: int, y: int) -> int:
return x + y
```

## unannotated-protocol-member

This error is raised when a protocol member is assigned a value in the class body without an explicit type annotation. Protocol members must have explicitly declared types so that implementations know exactly what type to provide.

```python
from typing import Protocol

class MyProto(Protocol):
x = None # error: Protocol member `x` must have an explicit type annotation

# Fixed version:
class MyProto(Protocol):
x: int | None = None
```

## unannotated-return

This error is raised when a function is missing a return type annotation. This helps enforce fully-typed codebases by ensuring all functions declare their return types explicitly. To fix it, add a return type annotation to the function.
Expand Down
Loading