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
124 changes: 62 additions & 62 deletions conformance/third_party/conformance.exp

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions crates/pyrefly_types/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,13 @@ impl<'a> TypeDisplayContext<'a> {
LspDisplayMode::Hover | LspDisplayMode::SignatureHelp => {
self.fmt_helper_generic(&func.clone().as_type(), false, output)
}
_ => self.fmt_helper_generic(&func.clone().as_type(), is_toplevel, output),
// Binding has already consumed the receiver, so showing it would
// misreport what the value can be called with, and would make a bound
// method indistinguishable from the unbound one it came from.
_ => {
let displayed = func.strip_receiver().unwrap_or_else(|| func.clone());
self.fmt_helper_generic(&displayed.as_type(), is_toplevel, output)
}
}
}
Type::Never(NeverStyle::NoReturn) => {
Expand Down Expand Up @@ -2681,7 +2687,7 @@ pub mod tests {
let mut ctx = TypeDisplayContext::new(&[&bound_method]);
assert_eq!(
ctx.display(&bound_method).to_string(),
"(self: Any, x: Any, y: Any) -> None"
"(x: Any, y: Any) -> None"
);
ctx.set_lsp_display_mode(LspDisplayMode::Hover);
assert_eq!(
Expand Down Expand Up @@ -2710,7 +2716,7 @@ pub mod tests {
let mut ctx = TypeDisplayContext::new(&[&bound_method]);
assert_eq!(
ctx.display(&bound_method).to_string(),
"[T](self: Any, x: Any, y: Any) -> None"
"[T](x: Any, y: Any) -> None"
);
ctx.set_lsp_display_mode(LspDisplayMode::Hover);
assert_eq!(
Expand Down Expand Up @@ -2739,7 +2745,7 @@ pub mod tests {
let mut ctx = TypeDisplayContext::new(&[&method]);
assert_eq!(
ctx.display(&method).to_string(),
"[T, **P, R](self: Any, x: Any, y: Any) -> None"
"[T, **P, R](x: Any, y: Any) -> None"
);
ctx.set_lsp_display_mode(LspDisplayMode::Hover);
assert_eq!(
Expand All @@ -2763,7 +2769,7 @@ pub mod tests {
let mut ctx = TypeDisplayContext::new(&[&method]);
assert_eq!(
ctx.display(&method).to_string(),
"[T, *Ts, R](self: Any, x: Any, y: Any) -> None"
"[T, *Ts, R](x: Any, y: Any) -> None"
);
ctx.set_lsp_display_mode(LspDisplayMode::Hover);
assert_eq!(
Expand Down Expand Up @@ -2868,15 +2874,15 @@ def overloaded_func[T](
let ctx = TypeDisplayContext::new(&[&bound_method_overload]);
assert_eq!(
ctx.display(&bound_method_overload).to_string(),
"Overload[\n (x: Any) -> None\n [T](x: Any, y: Any) -> None\n]"
"Overload[\n () -> None\n [T](y: Any) -> None\n]"
);

// Test compact display mode as non-toplevel type (non-hover)
let type_form_of_bound_method_overload = Type::type_of(bound_method_overload.clone());
let ctx = TypeDisplayContext::new(&[&type_form_of_bound_method_overload]);
assert_eq!(
ctx.display(&type_form_of_bound_method_overload).to_string(),
"type[Overload[(x: Any) -> None, [T](x: Any, y: Any) -> None]]"
"type[Overload[() -> None, [T](y: Any) -> None]]"
);

// Test hover display mode (with @overload decorators)
Expand Down
61 changes: 61 additions & 0 deletions crates/pyrefly_types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,67 @@ impl BoundMethodType {
}
}

/// The signature callers see, with the receiver parameter that binding already
/// consumed removed. Returns `None` when no parameter can be stripped, which
/// happens for signatures like `(...) -> T` that have no leading positional param.
///
/// This is a display-only view: `bind_bound_method_type` in the solver performs the
/// same strip, and additionally instantiates type parameters against the receiver.
pub fn strip_receiver(&self) -> Option<Self> {
match self {
Self::Function(func) => func.signature.strip_first_param().map(|signature| {
Self::Function(Function {
signature,
metadata: func.metadata.clone(),
})
}),
Self::Forall(forall) => forall.body.signature.strip_first_param().map(|signature| {
Self::Forall(Forall {
tparams: forall.tparams.clone(),
body: Function {
signature,
metadata: forall.body.metadata.clone(),
},
})
}),
Self::Overload(overload) => overload
.signatures
.try_mapped_ref(|x| match x {
OverloadType::Function(f) => f
.signature
.strip_first_param()
.map(|signature| {
OverloadType::Function(Function {
signature,
metadata: f.metadata.clone(),
})
})
.ok_or(()),
OverloadType::Forall(forall) => forall
.body
.signature
.strip_first_param()
.map(|signature| {
OverloadType::Forall(Forall {
tparams: forall.tparams.clone(),
body: Function {
signature,
metadata: forall.body.metadata.clone(),
},
})
})
.ok_or(()),
})
.ok()
.map(|signatures| {
Self::Overload(Overload {
signatures,
metadata: overload.metadata.clone(),
})
}),
}
}

pub fn subst_self_type_mut(&mut self, replacement: &Type) {
match self {
Self::Function(func) => func.signature.subst_self_type_mut(replacement),
Expand Down
20 changes: 10 additions & 10 deletions pyrefly/lib/error/signature_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B) -> None`, which is not assignable to `(self: B, a: int, b: int, c: int) -> Unknown`, the type of `A.foo`
`B.foo` has type `() -> None`, which is not assignable to `(a: int, b: int, c: int) -> Unknown`, the type of `A.foo`
Signature mismatch:
expected: def foo(self: B, a: int, b: int, c: int) -> Unknown: ...
^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ return type
Expand Down Expand Up @@ -294,7 +294,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B, x: int, y: str) -> None`, which is not assignable to `(self: B) -> None`, the type of `A.foo`
`B.foo` has type `(x: int, y: str) -> None`, which is not assignable to `() -> None`, the type of `A.foo`
Signature mismatch:
expected: def foo(self: B) -> None: ...
^ parameters
Expand All @@ -319,7 +319,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B, x: str) -> None`, which is not assignable to `(self: B, x: int) -> None`, the type of `A.foo`
`B.foo` has type `(x: str) -> None`, which is not assignable to `(x: int) -> None`, the type of `A.foo`
Signature mismatch:
expected: def foo(self: B, x: int) -> None: ...
^^^ parameters
Expand All @@ -344,7 +344,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B, x: int) -> str`, which is not assignable to `(self: B, x: int) -> int`, the type of `A.foo`
`B.foo` has type `(x: int) -> str`, which is not assignable to `(x: int) -> int`, the type of `A.foo`
Signature mismatch:
expected: def foo(self: B, x: int) -> int: ...
^^^ return type
Expand Down Expand Up @@ -377,9 +377,9 @@ class B(A):
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
// Overloads have multiple signatures, so no signature diff is shown.
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B, x: float) -> float`, which is not assignable to `Overload[
(self: B, x: int) -> int
(self: B, x: str) -> str
`B.foo` has type `(x: float) -> float`, which is not assignable to `Overload[
(x: int) -> int
(x: str) -> str
]`, the type of `A.foo`"#;
assert_eq!(messages[0], expected);
}
Expand All @@ -401,7 +401,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: B, x: str) -> str`, which is not assignable to `(self: B, x: int) -> int`, the type of `A.foo`
`B.foo` has type `(x: str) -> str`, which is not assignable to `(x: int) -> int`, the type of `A.foo`
Signature mismatch:
expected: def foo(self: B, x: int) -> int: ...
^^^ ^^^ return type
Expand Down Expand Up @@ -431,7 +431,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.foo` overrides parent class `A` in an inconsistent manner
`B.foo` has type `(self: Unknown) -> None`, which is not consistent with `(self: B, x: int) -> int` in `A.foo` (the type of read-write attributes cannot be changed)
`B.foo` has type `(self: Unknown) -> None`, which is not consistent with `(x: int) -> int` in `A.foo` (the type of read-write attributes cannot be changed)
Signature mismatch:
expected: def foo(self: B, x: int) -> int: ...
^^^^^^^^^ ^^^ return type
Expand Down Expand Up @@ -463,7 +463,7 @@ class B(A):
);
assert_eq!(messages.len(), 1, "Expected one error, got {messages:?}");
let expected = r#"Class member `B.method` overrides parent class `A` in an inconsistent manner
`B.method` has type `(self: Unknown) -> None`, which is not assignable to `(self: B, x: int) -> int`, the type of `A.method`
`B.method` has type `() -> None`, which is not assignable to `(x: int) -> int`, the type of `A.method`
Signature mismatch:
expected: def method(self: B, x: int) -> int: ...
^^^^^^^^^ ^^^ return type
Expand Down
6 changes: 3 additions & 3 deletions pyrefly/lib/test/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ def f2(c: Callable[[C, int], None]):
f1(C.f) # E: Argument `(self: C, x: int) -> None` is not assignable to parameter `c` with type `(int) -> None`
f1(C().f)
f2(C.f)
f2(C().f) # E: Argument `(self: C, x: int) -> None` is not assignable to parameter `c` with type `(C, int) -> None`
f2(C().f) # E: Argument `(x: int) -> None` is not assignable to parameter `c` with type `(C, int) -> None`
"#,
);

Expand Down Expand Up @@ -1129,7 +1129,7 @@ def test(o: D):
reveal_type(o.f) # E: [T](x: T) -> T
assert_type(o.f(1), int)

reveal_type(o.g) # E: [U](self: C, x: U) -> U
reveal_type(o.g) # E: [U](x: U) -> U
assert_type(o.g(1), int)
"#,
);
Expand Down Expand Up @@ -2286,7 +2286,7 @@ testcase!(
r#"
from typing import Never, assert_type, reveal_type
def f() -> type[Never]: ...
reveal_type(f().mro) # E: (self: type) -> list[type[Any]]
reveal_type(f().mro) # E: () -> list[type[Any]]
assert_type(f().wut, Never)
"#,
);
Expand Down
8 changes: 4 additions & 4 deletions pyrefly/lib/test/callable_residuals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class Wrapper[**P, R]:
def f[S](x: S) -> S: ...
wrapper = Wrapper(f)
reveal_type(wrapper.fn) # E: revealed type: [R](x: R) -> R
reveal_type(wrapper.__call__) # E: [R](self: Wrapper[[x: R], R], x: R) -> R
reveal_type(wrapper.__call__) # E: [R](x: R) -> R
assert_type(wrapper(1), int)
"#,
);
Expand All @@ -345,7 +345,7 @@ def wrap[**P, R](f: Callable[P, R]) -> Wrapper[P, R]:
def f[S](x: S) -> S: ...
wrapper = wrap(f)
reveal_type(wrapper.fn) # E: revealed type: [R](x: R) -> R
reveal_type(wrapper.__call__) # E: [R](self: Wrapper[[x: R], R], x: R) -> R
reveal_type(wrapper.__call__) # E: [R](x: R) -> R
assert_type(wrapper(1), int)
"#,
);
Expand All @@ -363,7 +363,7 @@ class Wrapper[**P, R]:
def f[S](x: S) -> S: ...
wrapper = Wrapper(f)
reveal_type(wrapper) # E: revealed type: Wrapper[[x: GenericResidual@R], GenericResidual@R]
reveal_type(wrapper.__call__) # E: [R](self: Wrapper[[x: R], R], x: R) -> R
reveal_type(wrapper.__call__) # E: [R](x: R) -> R
"#,
);

Expand Down Expand Up @@ -451,7 +451,7 @@ class Wrapper[**P, R]:
ctor = identity(Wrapper)
reveal_type(ctor) # E: revealed type: [**P, R](fn: (ParamSpec(P)) -> R) -> Wrapper[P, R]
identity2 = ctor(identity)
reveal_type(identity2.__call__) # E: revealed type: [**P, R](self: Wrapper[[x: (ParamSpec(P)) -> R], (ParamSpec(P)) -> R], x: (ParamSpec(P)) -> R) -> (ParamSpec(P)) -> R
reveal_type(identity2.__call__) # E: revealed type: [**P, R](x: (ParamSpec(P)) -> R) -> (ParamSpec(P)) -> R
"#,
);

Expand Down
6 changes: 3 additions & 3 deletions pyrefly/lib/test/class_overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ class A:
def f(self, x: TA1):
pass
class B(A):
def f(self, x: TA2): # E: `B.f` has type `(self: B, x: TA2) -> None`, which is not assignable to `(self: B, x: TA1) -> None`, the type of `A.f`
def f(self, x: TA2): # E: `B.f` has type `(x: TA2) -> None`, which is not assignable to `(x: TA1) -> None`, the type of `A.f`
pass
"#,
);
Expand Down Expand Up @@ -2030,7 +2030,7 @@ class Base:
pass

class ChildNarrowed(Base):
p: B # E: `ChildNarrowed.p` has type `B`, which is not assignable from `(self: ChildNarrowed, value: A) -> None`, the property setter for `Base.p`
p: B # E: `ChildNarrowed.p` has type `B`, which is not assignable from `(value: A) -> None`, the property setter for `Base.p`

class ChildSuppressed(Base):
p: B # pyrefly: ignore[bad-override-mutable-attribute]
Expand All @@ -2046,7 +2046,7 @@ class ChildWidened(Base):
# Property-to-property override with narrowed setter.
class ChildPropertyNarrowedSetter(Base):
@property
def p(self) -> A: # E: The property setter for `ChildPropertyNarrowedSetter.p` has type `(self: ChildPropertyNarrowedSetter, value: B) -> None`, which is not assignable from `(self: ChildPropertyNarrowedSetter, value: A) -> None`, the property setter for `Base.p`
def p(self) -> A: # E: The property setter for `ChildPropertyNarrowedSetter.p` has type `(value: B) -> None`, which is not assignable from `(value: A) -> None`, the property setter for `Base.p`
return A()
@p.setter
def p(self, value: B) -> None:
Expand Down
2 changes: 1 addition & 1 deletion pyrefly/lib/test/class_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class Parent:

class Child(Parent):
def __init__(self) -> None:
self.meth2 = super().meth1 # E: `(self: type[Self@Child]) -> int` is not assignable to attribute `meth2` with type `(self: type[Self@Child]) -> str`
self.meth2 = super().meth1 # E: `() -> int` is not assignable to attribute `meth2` with type `() -> str`

# At runtime, this is a call to the inherited `Parent.meth2` classmethod.
# We don't have a good way of modeling this, so we treat this as an (illegal) class access of the
Expand Down
2 changes: 1 addition & 1 deletion pyrefly/lib/test/dataclasses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2188,7 +2188,7 @@ class Bad1:
x: int
y: InitVar[str]
z: InitVar[bytes]
def __post_init__(self, y: bytes, z: str): ... # E: `__post_init__` type `(self: Bad1, y: bytes, z: str) -> None` is not assignable to expected type `(y: str, z: bytes) -> object` generated from the dataclass's `InitVar` fields
def __post_init__(self, y: bytes, z: str): ... # E: `__post_init__` type `(y: bytes, z: str) -> None` is not assignable to expected type `(y: str, z: bytes) -> object` generated from the dataclass's `InitVar` fields
@dataclass
class Bad2:
x: int
Expand Down
4 changes: 2 additions & 2 deletions pyrefly/lib/test/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class C:
def foo(cls) -> int:
return 42
def f(c: C):
reveal_type(C.foo) # E: revealed type: (cls: type[C]) -> int
reveal_type(c.foo) # E: revealed type: (cls: type[C]) -> int
reveal_type(C.foo) # E: revealed type: () -> int
reveal_type(c.foo) # E: revealed type: () -> int
"#,
);

Expand Down
4 changes: 2 additions & 2 deletions pyrefly/lib/test/named_tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ class Pair2[T](NamedTuple):
y: T

def test(p: Pair, p2: Pair2[bytes]):
reveal_type(p.__iter__) # E: (self: Pair) -> Iterator[int | str]
reveal_type(p2.__iter__) # E: (self: Pair2[bytes]) -> Iterator[bytes | int]
reveal_type(p.__iter__) # E: () -> Iterator[int | str]
reveal_type(p2.__iter__) # E: () -> Iterator[bytes | int]
"#,
);

Expand Down
2 changes: 1 addition & 1 deletion pyrefly/lib/test/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ class Asset(TestABC):

class PensionAsset(Asset):
@classmethod
def _money_desc(cls): # E: `PensionAsset._money_desc` has type `(cls: type[PensionAsset]) -> Literal['90岁累计可领(元)']`, which is not assignable to `(cls: type[PensionAsset]) -> Literal['累计可领(元)']`, the type of `Asset._money_desc`
def _money_desc(cls): # E: `PensionAsset._money_desc` has type `() -> Literal['90岁累计可领(元)']`, which is not assignable to `() -> Literal['累计可领(元)']`, the type of `Asset._money_desc`
return '90岁累计可领(元)'
"#,
);
Expand Down
2 changes: 1 addition & 1 deletion test/sarif/diagnostics.expected.sarif
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
"ruleIndex": 0,
"level": "error",
"message": {
"text": "`Mismatched` is not assignable to `Comparable`\n`Mismatched.compare` has type `(self: Mismatched) -> str`, which is not assignable to `(self: Mismatched) -> int`, the type of `Comparable.compare`"
"text": "`Mismatched` is not assignable to `Comparable`\n`Mismatched.compare` has type `() -> str`, which is not assignable to `() -> int`, the type of `Comparable.compare`"
},
"locations": [
{
Expand Down
Loading