[ty] Include type parameters in generic callable display (#22435)

This commit is contained in:
Dex Devlon
2026-01-13 22:59:08 +05:30
committed by GitHub
parent fde7d72fbb
commit 2f64ef9c72
14 changed files with 230 additions and 65 deletions

View File

@@ -3586,7 +3586,7 @@ quux.<CURSOR>
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
__module__ :: str
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__new__ :: def __new__[Self](cls) -> Self
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method Quux.__repr__() -> str
@@ -3667,19 +3667,19 @@ C.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__or__ :: bound method <class 'C'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls) -> Self
__or__ :: bound method <class 'C'>.__or__[Self](value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: bound method <class 'C'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__ror__ :: bound method <class 'C'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'C'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'C'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'C'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'C'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3737,18 +3737,18 @@ Meta.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__or__ :: def __or__[Self](self: Self@__or__, value: Any, /) -> UnionType | Self@__or__
__or__ :: def __or__[Self](self: Self, value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: def __ror__[Self](self: Self@__ror__, value: Any, /) -> UnionType | Self@__ror__
__ror__ :: def __ror__[Self](self: Self, value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
__subclasses__ :: def __subclasses__[Self](self: Self@__subclasses__) -> list[Self@__subclasses__]
__subclasses__ :: def __subclasses__[Self](self: Self) -> list[Self]
__subclasshook__ :: bound method <class 'Meta'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3866,19 +3866,19 @@ Quux.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__or__ :: bound method <class 'Quux'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls) -> Self
__or__ :: bound method <class 'Quux'>.__or__[Self](value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: bound method <class 'Quux'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__ror__ :: bound method <class 'Quux'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'Quux'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Quux'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'Quux'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'Quux'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3919,8 +3919,8 @@ Answer.<CURSOR>
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
__class__ :: <class 'EnumMeta'>
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
__copy__ :: def __copy__(self) -> Self@__copy__
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@__deepcopy__
__copy__ :: def __copy__[Self](self) -> Self
__deepcopy__ :: def __deepcopy__[Self](self, memo: Any) -> Self
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: dict[str, Any]
__dictoffset__ :: int
@@ -3930,34 +3930,34 @@ Answer.<CURSOR>
__flags__ :: int
__format__ :: def __format__(self, format_spec: str) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getitem__ :: bound method <class 'Answer'>.__getitem__[_EnumMemberT](name: str) -> _EnumMemberT@__getitem__
__getitem__ :: bound method <class 'Answer'>.__getitem__[_EnumMemberT](name: str) -> _EnumMemberT
__getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> None
__init_subclass__ :: bound method <class 'Answer'>.__init_subclass__() -> None
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT]
__len__ :: bound method <class 'Answer'>.__len__() -> int
__members__ :: MappingProxyType[str, Answer]
__module__ :: str
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls, value: object) -> Self@__new__
__or__ :: bound method <class 'Answer'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls, value: object) -> Self
__or__ :: bound method <class 'Answer'>.__or__[Self](value: Any, /) -> UnionType | Self
__order__ :: str
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT@__reversed__]
__ror__ :: bound method <class 'Answer'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT]
__ror__ :: bound method <class 'Answer'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Answer'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'Answer'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'Answer'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3999,7 +3999,7 @@ quux.<CURSOR>
index :: bound method Quux.index(value: Any, start: SupportsIndex = 0, stop: SupportsIndex = ..., /) -> int
x :: int
y :: str
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], (value: tuple[_T@__add__, ...], /) -> tuple[int | str | _T@__add__, ...]]
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], [_T](value: tuple[_T, ...], /) -> tuple[int | str | _T, ...]]
__annotations__ :: dict[str, Any]
__class__ :: type[Quux]
__class_getitem__ :: bound method type[Quux].__class_getitem__(item: Any, /) -> GenericAlias

View File

@@ -786,17 +786,17 @@ argument:
```py
from typing_extensions import Self
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
reveal_type(object.__new__) # revealed: def __new__[Self](cls) -> Self
reveal_type(object().__new__) # revealed: def __new__[Self](cls) -> Self
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type(int.__new__)
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type((42).__new__)
class X:
def __init__(self, val: int): ...
def make_another(self) -> Self:
reveal_type(self.__new__) # revealed: def __new__(cls) -> Self@__new__
reveal_type(self.__new__) # revealed: def __new__[Self](cls) -> Self
return self.__new__(type(self))
```

View File

@@ -1208,7 +1208,7 @@ def uses_dataclass[T](x: T) -> ChildOfParentDataclass[T]:
# revealed: (self: ParentDataclass[Unknown], value: Unknown) -> None
reveal_type(ParentDataclass.__init__)
# revealed: (self: ParentDataclass[T@ChildOfParentDataclass], value: T@ChildOfParentDataclass) -> None
# revealed: [T](self: ParentDataclass[T], value: T) -> None
reveal_type(ChildOfParentDataclass.__init__)
result_int = uses_dataclass(42)

View File

@@ -817,7 +817,7 @@ class WithOverloadedMethod(Generic[T]):
def method(self, x: S | T) -> S | T:
return x
# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
# revealed: Overload[(self, x: int) -> int, [S](self, x: S) -> S | int]
reveal_type(WithOverloadedMethod[int].method)
```

View File

@@ -702,7 +702,7 @@ class WithOverloadedMethod[T]:
def method[S](self, x: S | T) -> S | T:
return x
# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
# revealed: Overload[(self, x: int) -> int, [S](self, x: S) -> S | int]
reveal_type(WithOverloadedMethod[int].method)
```

View File

@@ -867,7 +867,7 @@ class ClassWithOverloadedInit[T]:
# overload. We would then also have to determine that R must be equal to the return type of **P's
# solution.
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
# revealed: Overload[[T](x: int) -> ClassWithOverloadedInit[int], [T](x: str) -> ClassWithOverloadedInit[str]]
reveal_type(into_callable(ClassWithOverloadedInit))
# TODO: revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str], (x: str) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]]
@@ -888,7 +888,7 @@ class GenericClass[T]:
def _(x: list[str]):
# TODO: This fails because we are not propagating GenericClass's generic context into the
# Callable that we create for it.
# revealed: (x: list[T@GenericClass], y: list[T@GenericClass]) -> GenericClass[T@GenericClass]
# revealed: [T](x: list[T], y: list[T]) -> GenericClass[T]
reveal_type(into_callable(GenericClass))
# revealed: ty_extensions.GenericContext[T@GenericClass]
reveal_type(generic_context(into_callable(GenericClass)))

View File

@@ -153,7 +153,7 @@ already solved and specialized when the class was specialized:
from ty_extensions import generic_context
legacy.m("string", None) # error: [invalid-argument-type]
reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S@m) -> S@m
reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S) -> S
# revealed: ty_extensions.GenericContext[T@Legacy]
reveal_type(generic_context(Legacy))
# revealed: ty_extensions.GenericContext[Self@m, S@m]
@@ -344,4 +344,27 @@ class C[T]:
ok2: Inner[T]
```
## Mixed-scope type parameters
Methods can have type parameters that are scoped to the method itself, while also referring to type
parameters from the enclosing class.
```py
from typing import Generic, TypeVar
from ty_extensions import into_callable
T = TypeVar("T")
S = TypeVar("S")
class Foo(Generic[T]):
def bar(self, x: T, y: S) -> tuple[T, S]:
raise NotImplementedError
def f(x: type[Foo[T]]) -> T:
# revealed: [S](self, x: T@f, y: S) -> tuple[T@f, S]
reveal_type(into_callable(x.bar))
raise NotImplementedError
```
[scoping]: https://typing.python.org/en/latest/spec/generics.html#scoping-rules-for-type-variables

View File

@@ -348,7 +348,7 @@ def expects_named_tuple(x: typing.NamedTuple):
reveal_type(x) # revealed: tuple[object, ...] & NamedTupleLike
reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> NamedTupleLike
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(...) -> NamedTupleLike
# revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]]
# revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], [_T](value: tuple[_T, ...], /) -> tuple[object, ...]]
reveal_type(x.__add__)
reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object]

View File

@@ -311,7 +311,7 @@ def func[T](x: T) -> T: ...
def func[T](x: T | None = None) -> T | None:
return x
reveal_type(func) # revealed: Overload[() -> None, (x: T@func) -> T@func]
reveal_type(func) # revealed: Overload[() -> None, [T](x: T) -> T]
reveal_type(func()) # revealed: None
reveal_type(func(1)) # revealed: Literal[1]
reveal_type(func("")) # revealed: Literal[""]

View File

@@ -11,7 +11,7 @@ def _(flag: bool) -> None:
abs = 1
chr: int = 1
reveal_type(abs) # revealed: Literal[1] | (def abs[_T](x: SupportsAbs[_T@abs], /) -> _T@abs)
reveal_type(abs) # revealed: Literal[1] | (def abs[_T](x: SupportsAbs[_T], /) -> _T)
reveal_type(chr) # revealed: Literal[1] | (def chr(i: SupportsIndex, /) -> str)
```

View File

@@ -200,7 +200,7 @@ info: Type variable defined here
| ^^^^^^
14 | return 0
|
info: Union variant `def f4[T](x: T@f4) -> int` is incompatible with this call site
info: Union variant `def f4[T](x: T) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
info: rule `invalid-argument-type` is enabled by default

View File

@@ -899,6 +899,16 @@ impl<'db> Type<'db> {
matches!(self, Type::Never)
}
/// Returns `true` if this type contains a `Self` type variable.
pub(crate) fn contains_self(&self, db: &'db dyn Db) -> bool {
any_over_type(
db,
*self,
&|ty| matches!(ty, Type::TypeVar(tv) if tv.typevar(db).is_self(db)),
false,
)
}
/// Returns `true` if `self` is [`Type::Callable`].
pub(crate) const fn is_callable_type(&self) -> bool {
matches!(self, Type::Callable(..))
@@ -6900,8 +6910,26 @@ impl<'db> TypeMapping<'_, 'db> {
context: GenericContext<'db>,
) -> GenericContext<'db> {
match self {
TypeMapping::ApplySpecialization(_)
| TypeMapping::UniqueSpecialization { .. }
TypeMapping::ApplySpecialization(specialization) => {
// Filter out type variables that are already specialized
// (i.e., mapped to a non-TypeVar type)
GenericContext::from_typevar_instances(
db,
context.variables(db).filter(|bound_typevar| {
// Keep the type variable if it's not in the specialization
// or if it's mapped to itself (still a TypeVar)
match specialization.get(db, *bound_typevar) {
None => true,
Some(Type::TypeVar(mapped_typevar)) => {
// Still a TypeVar, keep it if it's mapping to itself
mapped_typevar.identity(db) == bound_typevar.identity(db)
}
Some(_) => false, // Specialized to a concrete type, filter out
}
}),
)
}
TypeMapping::UniqueSpecialization { .. }
| TypeMapping::PromoteLiterals(_)
| TypeMapping::BindLegacyTypevars(_)
| TypeMapping::Materialize(_)

View File

@@ -26,10 +26,10 @@ use crate::types::signatures::{
use crate::types::tuple::TupleSpec;
use crate::types::visitor::TypeVisitor;
use crate::types::{
BoundTypeVarIdentity, CallableType, CallableTypeKind, IntersectionType, KnownBoundMethodType,
KnownClass, KnownInstanceType, MaterializationKind, Protocol, ProtocolInstanceType,
SpecialFormType, StringLiteralType, SubclassOfInner, Type, TypeGuardLike, TypedDictType,
UnionType, WrapperDescriptorKind, visitor,
BindingContext, BoundTypeVarIdentity, CallableType, CallableTypeKind, IntersectionType,
KnownBoundMethodType, KnownClass, KnownInstanceType, MaterializationKind, Protocol,
ProtocolInstanceType, SpecialFormType, StringLiteralType, SubclassOfInner, Type, TypeGuardLike,
TypedDictType, UnionType, WrapperDescriptorKind, visitor,
};
/// Settings for displaying types and signatures
@@ -45,6 +45,10 @@ pub struct DisplaySettings<'db> {
/// Disallow Signature printing to introduce a name
/// (presumably because we rendered one already)
pub disallow_signature_name: bool,
/// Scopes that are currently active in the display context (e.g. function scopes
/// whose type parameters are currently being displayed).
/// Used to suppress redundant `@{scope}` suffixes for type variables.
pub active_scopes: Rc<FxHashSet<Definition<'db>>>,
}
impl<'db> DisplaySettings<'db> {
@@ -88,6 +92,16 @@ impl<'db> DisplaySettings<'db> {
}
}
#[must_use]
pub fn with_active_scopes(&self, scopes: impl IntoIterator<Item = Definition<'db>>) -> Self {
let mut active_scopes = (*self.active_scopes).clone();
active_scopes.extend(scopes);
Self {
active_scopes: Rc::new(active_scopes),
..self.clone()
}
}
#[must_use]
pub fn from_possibly_ambiguous_types<I, T>(db: &'db dyn Db, types: I) -> Self
where
@@ -753,7 +767,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
write!(
f.with_type(Type::TypeVar(bound_typevar)),
"{}",
bound_typevar.identity(self.db).display(self.db)
bound_typevar
.identity(self.db)
.display_with(self.db, self.settings.clone())
)?;
f.write_char(']')
}
@@ -778,10 +794,14 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
match function.signature(self.db).overloads.as_slice() {
[signature] => {
let bound_signature = signature.bind_self(self.db, Some(typing_self_ty));
let hide_unused_self =
bound_signature.should_hide_self_from_display(self.db);
let type_parameters = DisplayOptionalGenericContext {
generic_context: signature.generic_context.as_ref(),
generic_context: bound_signature.generic_context.as_ref(),
db: self.db,
settings: self.settings.clone(),
hide_unused_self,
};
f.set_invalid_type_annotation();
f.write_str("bound method ")?;
@@ -791,8 +811,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
f.write_char('.')?;
f.with_type(self.ty).write_str(function.name(self.db))?;
type_parameters.fmt_detailed(f)?;
signature
.bind_self(self.db, Some(typing_self_ty))
bound_signature
.display_with(self.db, self.settings.disallow_signature_name())
.fmt_detailed(f)
}
@@ -984,7 +1003,13 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
}
Type::TypeVar(bound_typevar) => {
f.set_invalid_type_annotation();
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
write!(
f,
"{}",
bound_typevar
.identity(self.db)
.display_with(self.db, self.settings.clone())
)
}
Type::AlwaysTruthy => f.with_type(self.ty).write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f.with_type(self.ty).write_str("AlwaysFalsy"),
@@ -1049,6 +1074,19 @@ impl<'db> BoundTypeVarIdentity<'db> {
DisplayBoundTypeVarIdentity {
bound_typevar_identity: self,
db,
settings: DisplaySettings::default(),
}
}
pub(crate) fn display_with(
self,
db: &'db dyn Db,
settings: DisplaySettings<'db>,
) -> impl Display {
DisplayBoundTypeVarIdentity {
bound_typevar_identity: self,
db,
settings,
}
}
}
@@ -1056,13 +1094,18 @@ impl<'db> BoundTypeVarIdentity<'db> {
struct DisplayBoundTypeVarIdentity<'db> {
bound_typevar_identity: BoundTypeVarIdentity<'db>,
db: &'db dyn Db,
settings: DisplaySettings<'db>,
}
impl Display for DisplayBoundTypeVarIdentity<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.bound_typevar_identity.identity.name(self.db))?;
if let Some(binding_context) = self.bound_typevar_identity.binding_context.name(self.db) {
write!(f, "@{binding_context}")?;
let binding_context = self.bound_typevar_identity.binding_context;
if let Some(binding_context_name) = binding_context.name(self.db)
&& let Some(definition) = binding_context.definition()
&& !self.settings.active_scopes.contains(&definition)
{
write!(f, "@{binding_context_name}")?;
}
if let Some(paramspec_attr) = self.bound_typevar_identity.paramspec_attr {
write!(f, ".{paramspec_attr}")?;
@@ -1193,10 +1236,12 @@ pub(crate) struct DisplayOverloadLiteral<'db> {
impl<'db> FmtDetailed<'db> for DisplayOverloadLiteral<'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
let signature = self.literal.signature(self.db);
let hide_unused_self = signature.should_hide_self_from_display(self.db);
let type_parameters = DisplayOptionalGenericContext {
generic_context: signature.generic_context.as_ref(),
db: self.db,
settings: self.settings.clone(),
hide_unused_self,
};
f.set_invalid_type_annotation();
@@ -1241,10 +1286,13 @@ impl<'db> FmtDetailed<'db> for DisplayFunctionType<'db> {
match signature.overloads.as_slice() {
[signature] => {
let hide_unused_self = signature.should_hide_self_from_display(self.db);
let type_parameters = DisplayOptionalGenericContext {
generic_context: signature.generic_context.as_ref(),
db: self.db,
settings: self.settings.clone(),
hide_unused_self,
};
f.set_invalid_type_annotation();
f.write_str("def ")?;
@@ -1361,6 +1409,7 @@ impl<'db> GenericContext<'db> {
db,
settings: DisplaySettings::default(),
full: true,
hide_unused_self: false,
}
}
@@ -1374,6 +1423,7 @@ impl<'db> GenericContext<'db> {
db,
settings,
full: false,
hide_unused_self: false,
}
}
}
@@ -1382,14 +1432,22 @@ struct DisplayOptionalGenericContext<'a, 'db> {
generic_context: Option<&'a GenericContext<'db>>,
db: &'db dyn Db,
settings: DisplaySettings<'db>,
/// If true, hide `Self` type variables from the generic context prefix
/// when they are not displayed in the signature body.
hide_unused_self: bool,
}
impl<'db> FmtDetailed<'db> for DisplayOptionalGenericContext<'_, 'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
if let Some(generic_context) = self.generic_context {
generic_context
.display_with(self.db, self.settings.clone())
.fmt_detailed(f)
DisplayGenericContext {
generic_context,
db: self.db,
settings: self.settings.clone(),
full: false,
hide_unused_self: self.hide_unused_self,
}
.fmt_detailed(f)
} else {
Ok(())
}
@@ -1408,22 +1466,27 @@ pub struct DisplayGenericContext<'a, 'db> {
#[expect(dead_code)]
settings: DisplaySettings<'db>,
full: bool,
/// If true, hide `Self` type variables from the generic context prefix.
hide_unused_self: bool,
}
impl<'db> DisplayGenericContext<'_, 'db> {
fn fmt_normal(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
let variables = self.generic_context.variables(self.db);
let mut variables = self
.generic_context
.variables(self.db)
.filter(|bound_typevar| {
// If hide_unused_self is true and this is a Self typevar, skip it
!self.hide_unused_self || !bound_typevar.typevar(self.db).is_self(self.db)
})
.peekable();
let non_implicit_variables: Vec<_> = variables
.filter(|bound_typevar| !bound_typevar.typevar(self.db).is_self(self.db))
.collect();
if non_implicit_variables.is_empty() {
if variables.peek().is_none() {
return Ok(());
}
f.write_char('[')?;
for (idx, bound_typevar) in non_implicit_variables.iter().enumerate() {
for (idx, bound_typevar) in variables.enumerate() {
if idx > 0 {
f.write_str(", ")?;
}
@@ -1433,12 +1496,14 @@ impl<'db> DisplayGenericContext<'_, 'db> {
f.write_str("**")?;
}
write!(
f.with_type(Type::TypeVar(*bound_typevar)),
f.with_type(Type::TypeVar(bound_typevar)),
"{}",
typevar.name(self.db)
)?;
}
f.write_char(']')
f.write_char(']')?;
Ok(())
}
fn fmt_full(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
@@ -1678,6 +1743,7 @@ impl<'db> Signature<'db> {
) -> DisplaySignature<'a, 'db> {
DisplaySignature {
definition: self.definition(),
generic_context: self.generic_context.as_ref(),
parameters: self.parameters(),
return_ty: self.return_ty,
db,
@@ -1688,13 +1754,14 @@ impl<'db> Signature<'db> {
pub(crate) struct DisplaySignature<'a, 'db> {
definition: Option<Definition<'db>>,
generic_context: Option<&'a GenericContext<'db>>,
parameters: &'a Parameters<'db>,
return_ty: Type<'db>,
db: &'db dyn Db,
settings: DisplaySettings<'db>,
}
impl DisplaySignature<'_, '_> {
impl<'db> DisplaySignature<'_, 'db> {
/// Get detailed display information including component ranges
pub(crate) fn to_string_parts(&self) -> SignatureDisplayDetails {
let mut f = TypeWriter::Details(TypeDetailsWriter::new());
@@ -1705,6 +1772,14 @@ impl DisplaySignature<'_, '_> {
TypeWriter::Formatter(_) => unreachable!("Expected Details variant"),
}
}
pub(crate) fn should_hide_self_from_display(&self, db: &'db dyn Db) -> bool {
!self.return_ty.contains_self(db)
&& !self
.parameters
.iter()
.any(|p| p.should_annotation_be_displayed() && p.annotated_type().contains_self(db))
}
}
impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> {
@@ -1730,15 +1805,41 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> {
f.write_str(&name)?;
}
let settings = if let Some(generic_context) = self.generic_context {
self.settings
.with_active_scopes(generic_context.variables(self.db).filter_map(|bound| {
match bound.binding_context(self.db) {
BindingContext::Definition(def) => Some(def),
BindingContext::Synthetic => None,
}
}))
} else {
self.settings.clone()
};
// Display type parameters if present, but only when the caller hasn't
// already displayed them (indicated by disallow_signature_name being false)
if !self.settings.disallow_signature_name {
let hide_unused_self = self.should_hide_self_from_display(self.db);
DisplayOptionalGenericContext {
generic_context: self.generic_context,
db: self.db,
settings: settings.clone(),
hide_unused_self,
}
.fmt_detailed(&mut f)?;
}
// Parameters
self.parameters
.display_with(self.db, self.settings.clone())
.display_with(self.db, settings.clone())
.fmt_detailed(&mut f)?;
// Return type
f.write_str(" -> ")?;
self.return_ty
.display_with(self.db, self.settings.singleline())
.display_with(self.db, settings.singleline())
.fmt_detailed(&mut f)?;
if self.parameters.is_top() {

View File

@@ -713,6 +713,19 @@ impl<'db> Signature<'db> {
Self::new(Parameters::bottom(), Type::Never)
}
/// Returns `true` if `Self` should be hidden from the generic context display.
///
/// `Self` is hidden if it does not appear in:
/// 1. The return type
/// 2. Any explicitly annotated parameter (not inferred)
pub(crate) fn should_hide_self_from_display(&self, db: &'db dyn Db) -> bool {
!self.return_ty.contains_self(db)
&& !self
.parameters()
.iter()
.any(|p| p.should_annotation_be_displayed() && p.annotated_type().contains_self(db))
}
pub(crate) fn with_inherited_generic_context(
mut self,
db: &'db dyn Db,