diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index df3ac6acd6..7d9823f159 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -588,6 +588,8 @@ mod tests { y = x z = i(1) w = z + aa = b'foo' + bb = aa ", ); @@ -599,6 +601,8 @@ mod tests { y[: Literal[1]] = x z[: int] = i(1) w[: int] = z + aa = b'foo' + bb[: Literal[b"foo"]] = aa --------------------------------------------- info[inlay-hint-location]: Inlay Hint Target @@ -630,12 +634,12 @@ mod tests { 350 | int(x, base=10) -> integer | info: Source - --> main2.py:7:5 + --> main2.py:6:13 | 5 | x = 1 6 | y[: Literal[1]] = x + | ^ 7 | z[: int] = i(1) - | ^^^ 8 | w[: int] = z | @@ -649,13 +653,71 @@ mod tests { 350 | int(x, base=10) -> integer | info: Source - --> main2.py:8:5 + --> main2.py:7:5 | + 5 | x = 1 6 | y[: Literal[1]] = x 7 | z[: int] = i(1) - 8 | w[: int] = z | ^^^ + 8 | w[: int] = z + 9 | aa = b'foo' | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:5 + | + 6 | y[: Literal[1]] = x + 7 | z[: int] = i(1) + 8 | w[: int] = z + | ^^^ + 9 | aa = b'foo' + 10 | bb[: Literal[b"foo"]] = aa + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/typing.pyi:351:1 + | + 349 | Final: _SpecialForm + 350 | + 351 | Literal: _SpecialForm + | ^^^^^^^ + 352 | TypedDict: _SpecialForm + | + info: Source + --> main2.py:10:6 + | + 8 | w[: int] = z + 9 | aa = b'foo' + 10 | bb[: Literal[b"foo"]] = aa + | ^^^^^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:1448:7 + | + 1447 | @disjoint_base + 1448 | class bytes(Sequence[int]): + | ^^^^^ + 1449 | """bytes(iterable_of_ints) -> bytes + 1450 | bytes(string, encoding[, errors]) -> bytes + | + info: Source + --> main2.py:10:14 + | + 8 | w[: int] = z + 9 | aa = b'foo' + 10 | bb[: Literal[b"foo"]] = aa + | ^^^^^^ + | "#); } @@ -706,6 +768,25 @@ mod tests { 10 | x4[: int], y4[: str] = (x3, y3) | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:14 + | + 7 | x1, y1 = (1, 'abc') + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + | ^ + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + 10 | x4[: int], y4[: str] = (x3, y3) + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/typing.pyi:351:1 | @@ -725,6 +806,25 @@ mod tests { 10 | x4[: int], y4[: str] = (x3, y3) | + 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:8:32 + | + 7 | x1, y1 = (1, 'abc') + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + | ^^^^^ + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + 10 | x4[: int], y4[: str] = (x3, y3) + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/builtins.pyi:348:7 | @@ -848,6 +948,25 @@ mod tests { 10 | x4[: int], y4[: str] = x3, y3 | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:14 + | + 7 | x1, y1 = 1, 'abc' + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + | ^ + 9 | x3[: int], y3[: str] = i(1), s('abc') + 10 | x4[: int], y4[: str] = x3, y3 + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/typing.pyi:351:1 | @@ -867,6 +986,25 @@ mod tests { 10 | x4[: int], y4[: str] = x3, y3 | + 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:8:32 + | + 7 | x1, y1 = 1, 'abc' + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + | ^^^^^ + 9 | x3[: int], y3[: str] = i(1), s('abc') + 10 | x4[: int], y4[: str] = x3, y3 + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/builtins.pyi:348:7 | @@ -1008,6 +1146,25 @@ mod tests { 10 | w[: tuple[int, str]] = z | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:19 + | + 7 | x = (1, 'abc') + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + | ^ + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + 10 | w[: tuple[int, str]] = z + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/typing.pyi:351:1 | @@ -1027,6 +1184,25 @@ mod tests { 10 | w[: tuple[int, str]] = z | + 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:8:31 + | + 7 | x = (1, 'abc') + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + | ^^^^^ + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + 10 | w[: tuple[int, str]] = z + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/builtins.pyi:2695:7 | @@ -1183,6 +1359,25 @@ mod tests { 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:14 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + | ^ + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/typing.pyi:351:1 | @@ -1202,6 +1397,25 @@ mod tests { 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) | + 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:8:33 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + | ^^^^^ + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/typing.pyi:351:1 | @@ -1221,6 +1435,25 @@ mod tests { 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:55 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + | ^ + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/builtins.pyi:348:7 | @@ -1375,6 +1608,25 @@ mod tests { 8 | w[: int] = z | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:6:13 + | + 5 | x: int = 1 + 6 | y[: Literal[1]] = x + | ^ + 7 | z: int = i(1) + 8 | w[: int] = z + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/builtins.pyi:348:7 | @@ -4988,6 +5240,78 @@ mod tests { | ^^^^^^^ | + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:13:17 + | + 11 | else: + 12 | x = None + 13 | y[: Literal[1, 2, 3, "hello"] | None] = x + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:13:20 + | + 11 | else: + 12 | x = None + 13 | y[: Literal[1, 2, 3, "hello"] | None] = x + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:13:23 + | + 11 | else: + 12 | x = None + 13 | y[: Literal[1, 2, 3, "hello"] | None] = x + | ^ + | + + 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:13:26 + | + 11 | else: + 12 | x = None + 13 | y[: Literal[1, 2, 3, "hello"] | None] = x + | ^^^^^^^ + | + info[inlay-hint-location]: Inlay Hint Target --> stdlib/types.pyi:950:11 | @@ -5123,6 +5447,45 @@ mod tests { "#); } + #[test] + fn test_property_literal_type() { + let mut test = inlay_hint_test( + r" + class F: + @property + def whatever(self): ... + + ab = F.whatever", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class F: + @property + def whatever(self): ... + + ab[: property] = F.whatever + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:4:9 + | + 2 | class F: + 3 | @property + 4 | def whatever(self): ... + | ^^^^^^^^ + 5 | + 6 | ab = F.whatever + | + info: Source + --> main2.py:6:6 + | + 4 | def whatever(self): ... + 5 | + 6 | ab[: property] = F.whatever + | ^^^^^^^^ + | + "); + } + #[test] fn test_complex_parameter_combinations() { let mut test = inlay_hint_test( diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dde55be126..556d5c54e8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7601,6 +7601,11 @@ impl<'db> Type<'db> { Self::TypeAlias(alias) => alias.value_type(db).definition(db), Self::NewTypeInstance(newtype) => Some(TypeDefinition::NewType(newtype.definition(db))), + Self::PropertyInstance(property) => property + .getter(db) + .and_then(|getter|getter.definition(db)) + .or_else(||property.setter(db).and_then(|setter|setter.definition(db))), + Self::StringLiteral(_) | Self::BooleanLiteral(_) | Self::LiteralString @@ -7612,7 +7617,6 @@ impl<'db> Type<'db> { | Self::WrapperDescriptor(_) | Self::DataclassDecorator(_) | Self::DataclassTransformer(_) - | Self::PropertyInstance(_) | Self::BoundSuper(_) => self.to_meta_type(db).definition(db), Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)), @@ -7622,20 +7626,22 @@ impl<'db> Type<'db> { Protocol::Synthesized(_) => None, }, - Type::TypedDict(typed_dict) => { + Self::TypedDict(typed_dict) => { Some(TypeDefinition::Class(typed_dict.defining_class().definition(db))) } Self::Union(_) | Self::Intersection(_) => None, Self::SpecialForm(special_form) => special_form.definition(db), + Self::Never => Type::SpecialForm(SpecialFormType::Never).definition(db), + Self::Dynamic(DynamicType::Any) => Type::SpecialForm(SpecialFormType::Any).definition(db), + Self::Dynamic(DynamicType::Unknown) => Type::SpecialForm(SpecialFormType::Unknown).definition(db), + Self::AlwaysTruthy => Type::SpecialForm(SpecialFormType::AlwaysTruthy).definition(db), + Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db), // These types have no definition - Self::Dynamic(_) - | Self::Never + Self::Dynamic(DynamicType::Divergent(_) | DynamicType::Todo(_) | DynamicType::TodoTypeAlias | DynamicType::TodoUnpack) | Self::Callable(_) - | Self::AlwaysTruthy - | Self::AlwaysFalsy | Self::TypeIs(_) => None, } } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 52f6dd7572..d38e465b98 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -23,9 +23,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu use crate::types::tuple::TupleSpec; use crate::types::visitor::TypeVisitor; use crate::types::{ - BoundTypeVarIdentity, CallableType, DynamicType, IntersectionType, KnownBoundMethodType, - KnownClass, MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, - StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor, + BoundTypeVarIdentity, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, + MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType, + SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor, }; use ruff_db::parsed::parsed_module; @@ -611,9 +611,7 @@ impl Display for DisplayRepresentation<'_> { impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { match self.ty { - Type::Dynamic(dynamic) => dynamic - .display_with(self.db, self.settings.clone()) - .fmt_detailed(f), + Type::Dynamic(dynamic) => write!(f.with_detail(TypeDetail::Type(self.ty)), "{dynamic}"), Type::Never => f.with_detail(TypeDetail::Type(self.ty)).write_str("Never"), Type::NominalInstance(instance) => { let class = instance.class(self.db); @@ -662,7 +660,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { f.write_char('>') } }, - Type::PropertyInstance(_) => f.write_str("property"), + Type::PropertyInstance(_) => f + .with_detail(TypeDetail::Type(self.ty)) + .write_str("property"), Type::ModuleLiteral(module) => { write!( f.with_detail(TypeDetail::Type(self.ty)), @@ -709,9 +709,10 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { f.with_detail(TypeDetail::Type(KnownClass::Type.to_class_literal(self.db))) .write_str("type")?; f.write_char('[')?; - dynamic - .display_with(self.db, self.settings.clone()) - .fmt_detailed(f)?; + write!( + f.with_detail(TypeDetail::Type(Type::Dynamic(dynamic))), + "{dynamic}" + )?; f.write_char(']') } }, @@ -847,18 +848,33 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { Type::Intersection(intersection) => intersection .display_with(self.db, self.settings.clone()) .fmt_detailed(f), - Type::IntLiteral(n) => write!(f, "{n}"), - Type::BooleanLiteral(boolean) => f.write_str(if boolean { "True" } else { "False" }), + Type::IntLiteral(n) => write!(f.with_detail(TypeDetail::Type(self.ty)), "{n}"), + Type::BooleanLiteral(boolean) => f + .with_detail(TypeDetail::Type(self.ty)) + .write_str(if boolean { "True" } else { "False" }), Type::StringLiteral(string) => { - write!(f, "{}", string.display_with(self.db, self.settings.clone())) + write!( + f.with_detail(TypeDetail::Type(self.ty)), + "{}", + string.display_with(self.db, self.settings.clone()) + ) } + // an alternative would be to use `Type::SpecialForm(SpecialFormType::LiteralString)` here, + // which would mean users would be able to jump to the definition of `LiteralString` from the + // inlay hint, but that seems less useful than the definition of `str` for a variable that is + // inferred as an *inhabitant* of `LiteralString` (since that variable will just be a string + // at runtime) Type::LiteralString => f .with_detail(TypeDetail::Type(self.ty)) .write_str("LiteralString"), Type::BytesLiteral(bytes) => { let escape = AsciiEscape::with_preferred_quote(bytes.value(self.db), Quote::Double); - escape.bytes_repr(TripleQuotes::No).write(f) + write!( + f.with_detail(TypeDetail::Type(self.ty)), + "{}", + escape.bytes_repr(TripleQuotes::No) + ) } Type::EnumLiteral(enum_literal) => { enum_literal @@ -1162,48 +1178,6 @@ impl Display for DisplayFunctionType<'_> { } } -impl<'db> DynamicType<'db> { - fn display_with<'a>( - &'a self, - db: &'db dyn Db, - settings: DisplaySettings<'db>, - ) -> DisplayDynamicType<'a, 'db> { - DisplayDynamicType { - dynamic_type: self, - db, - settings, - } - } -} - -struct DisplayDynamicType<'a, 'db> { - dynamic_type: &'a DynamicType<'db>, - #[allow(dead_code)] - db: &'db dyn Db, - #[allow(dead_code)] - settings: DisplaySettings<'db>, -} - -impl<'db> FmtDetailed<'db> for DisplayDynamicType<'_, 'db> { - fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { - match self.dynamic_type { - DynamicType::Any => write!( - f.with_detail(TypeDetail::Type(Type::SpecialForm(SpecialFormType::Any))), - "{}", - self.dynamic_type, - ), - DynamicType::Unknown => write!( - f.with_detail(TypeDetail::Type(Type::SpecialForm( - SpecialFormType::Unknown - ))), - "{}", - self.dynamic_type, - ), - _ => write!(f, "{}", self.dynamic_type), - } - } -} - impl<'db> GenericAlias<'db> { pub(crate) fn display(self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { self.display_with(db, DisplaySettings::default()) @@ -1237,16 +1211,20 @@ impl<'db> FmtDetailed<'db> for DisplayGenericAlias<'db> { .display_with(self.db, self.settings.clone()) .fmt_detailed(f) } else { - let prefix = match self.specialization.materialization_kind(self.db) { - None => "", - Some(MaterializationKind::Top) => "Top[", - Some(MaterializationKind::Bottom) => "Bottom[", + let prefix_details = match self.specialization.materialization_kind(self.db) { + None => None, + Some(MaterializationKind::Top) => Some(("Top", SpecialFormType::Top)), + Some(MaterializationKind::Bottom) => Some(("Bottom", SpecialFormType::Bottom)), }; let suffix = match self.specialization.materialization_kind(self.db) { None => "", Some(_) => "]", }; - f.write_str(prefix)?; + if let Some((name, form)) = prefix_details { + f.with_detail(TypeDetail::Type(Type::SpecialForm(form))) + .write_str(name)?; + f.write_char('[')?; + } self.origin .display_with(self.db, self.settings.clone()) .fmt_detailed(f)?;