[ty] Add more TypeDetails to the display code (#21541)

As far as I know this change is largely non-functional, largely because
of https://github.com/astral-sh/ty/issues/1601

It's possible some of these like `Type::KnownInstance` produce something
useful sometimes. `LiteralString` is a new introduction, although its
goto-type jumps to `str` which is a bit sad (considering that part of
the SpecialForm discourse for now).

Also wrt the generics testing followup: turns out the snapshot tests
were full of those already.
This commit is contained in:
Aria Desires 2025-11-20 12:08:59 -05:00 committed by GitHub
parent 0761ea42d9
commit 78ce17ce8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 13 deletions

View File

@ -4143,6 +4143,43 @@ mod tests {
"); ");
} }
#[test]
fn test_literal_string() {
let mut test = inlay_hint_test(
r#"
from typing import LiteralString
def my_func(x: LiteralString):
y = x
my_func(x="hello")"#,
);
assert_snapshot!(test.inlay_hints(), @r#"
from typing import LiteralString
def my_func(x: LiteralString):
y[: LiteralString] = x
my_func(x="hello")
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:915:7
|
914 | @disjoint_base
915 | class str(Sequence[str]):
| ^^^
916 | """str(object='') -> str
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main2.py:4:9
|
2 | from typing import LiteralString
3 | def my_func(x: LiteralString):
4 | y[: LiteralString] = x
| ^^^^^^^^^^^^^
5 | my_func(x="hello")
|
"#);
}
#[test] #[test]
fn test_complex_parameter_combinations() { fn test_complex_parameter_combinations() {
let mut test = inlay_hint_test( let mut test = inlay_hint_test(

View File

@ -24,9 +24,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
use crate::types::tuple::TupleSpec; use crate::types::tuple::TupleSpec;
use crate::types::visitor::TypeVisitor; use crate::types::visitor::TypeVisitor;
use crate::types::{ use crate::types::{
BoundTypeVarIdentity, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, BoundTypeVarIdentity, CallableType, DynamicType, IntersectionType, KnownBoundMethodType,
MaterializationKind, Protocol, ProtocolInstanceType, StringLiteralType, SubclassOfInner, Type, KnownClass, MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType,
UnionType, WrapperDescriptorKind, visitor, StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor,
}; };
use ruff_db::parsed::parsed_module; use ruff_db::parsed::parsed_module;
@ -470,7 +470,11 @@ impl<'db> FmtDetailed<'db> for DisplayType<'db> {
| Type::StringLiteral(_) | Type::StringLiteral(_)
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) => { | Type::EnumLiteral(_) => {
f.write_str("Literal[")?; f.with_detail(TypeDetail::Type(Type::SpecialForm(
SpecialFormType::Literal,
)))
.write_str("Literal")?;
f.write_char('[')?;
representation.fmt_detailed(f)?; representation.fmt_detailed(f)?;
f.write_str("]") f.write_str("]")
} }
@ -608,7 +612,16 @@ impl Display for DisplayRepresentation<'_> {
impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
match self.ty { match self.ty {
Type::Dynamic(dynamic) => write!(f, "{dynamic}"), Type::Dynamic(dynamic) => {
if let DynamicType::Any = dynamic {
write!(
f.with_detail(TypeDetail::Type(Type::SpecialForm(SpecialFormType::Any))),
"{dynamic}"
)
} else {
write!(f, "{dynamic}")
}
}
Type::Never => f.with_detail(TypeDetail::Type(self.ty)).write_str("Never"), Type::Never => f.with_detail(TypeDetail::Type(self.ty)).write_str("Never"),
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
let class = instance.class(self.db); let class = instance.class(self.db);
@ -638,7 +651,12 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
.fmt_detailed(f), .fmt_detailed(f),
}, },
Protocol::Synthesized(synthetic) => { Protocol::Synthesized(synthetic) => {
f.write_str("<Protocol with members ")?; f.write_char('<')?;
f.with_detail(TypeDetail::Type(Type::SpecialForm(
SpecialFormType::Protocol,
)))
.write_str("Protocol")?;
f.write_str(" with members ")?;
let interface = synthetic.interface(); let interface = synthetic.interface();
let member_list = interface.members(self.db); let member_list = interface.members(self.db);
let num_members = member_list.len(); let num_members = member_list.len();
@ -687,8 +705,14 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
} }
SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"), SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"),
}, },
Type::SpecialForm(special_form) => write!(f, "{special_form}"), Type::SpecialForm(special_form) => {
Type::KnownInstance(known_instance) => write!(f, "{}", known_instance.repr(self.db)), write!(f.with_detail(TypeDetail::Type(self.ty)), "{special_form}")
}
Type::KnownInstance(known_instance) => write!(
f.with_detail(TypeDetail::Type(self.ty)),
"{}",
known_instance.repr(self.db)
),
Type::FunctionLiteral(function) => function Type::FunctionLiteral(function) => function
.display_with(self.db, self.settings.clone()) .display_with(self.db, self.settings.clone())
.fmt_detailed(f), .fmt_detailed(f),
@ -724,7 +748,10 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
signatures => { signatures => {
// TODO: How to display overloads? // TODO: How to display overloads?
if !self.settings.multiline { if !self.settings.multiline {
f.write_str("Overload[")?; // TODO: This should ideally have a TypeDetail but we actually
// don't have a type for @overload (we just detect the decorator)
f.write_str("Overload")?;
f.write_char('[')?;
} }
let separator = if self.settings.multiline { "\n" } else { ", " }; let separator = if self.settings.multiline { "\n" } else { ", " };
let mut join = f.join(separator); let mut join = f.join(separator);
@ -815,7 +842,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
Type::StringLiteral(string) => { Type::StringLiteral(string) => {
write!(f, "{}", string.display_with(self.db, self.settings.clone())) write!(f, "{}", string.display_with(self.db, self.settings.clone()))
} }
Type::LiteralString => f.write_str("LiteralString"), Type::LiteralString => f
.with_detail(TypeDetail::Type(self.ty))
.write_str("LiteralString"),
Type::BytesLiteral(bytes) => { Type::BytesLiteral(bytes) => {
let escape = AsciiEscape::with_preferred_quote(bytes.value(self.db), Quote::Double); let escape = AsciiEscape::with_preferred_quote(bytes.value(self.db), Quote::Double);
@ -831,8 +860,12 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
Type::TypeVar(bound_typevar) => { Type::TypeVar(bound_typevar) => {
write!(f, "{}", bound_typevar.identity(self.db).display(self.db)) write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
} }
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysTruthy => f
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), .with_detail(TypeDetail::Type(self.ty))
.write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f
.with_detail(TypeDetail::Type(self.ty))
.write_str("AlwaysFalsy"),
Type::BoundSuper(bound_super) => { Type::BoundSuper(bound_super) => {
f.write_str("<super: ")?; f.write_str("<super: ")?;
Type::from(bound_super.pivot_class(self.db)) Type::from(bound_super.pivot_class(self.db))
@ -845,7 +878,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
f.write_str(">") f.write_str(">")
} }
Type::TypeIs(type_is) => { Type::TypeIs(type_is) => {
f.write_str("TypeIs[")?; f.with_detail(TypeDetail::Type(Type::SpecialForm(SpecialFormType::TypeIs)))
.write_str("TypeIs")?;
f.write_char('[')?;
type_is type_is
.return_type(self.db) .return_type(self.db)
.display_with(self.db, self.settings.singleline()) .display_with(self.db, self.settings.singleline())