[ty] Inlay Hint edit follow up (#21621)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

Don't allow edits of some more invalid syntax types.

## Test Plan

Add a test for `x = Literal['a']` (similar) to show we don't allow
edits.
This commit is contained in:
Matthew Mckee 2025-11-25 13:56:14 +00:00 committed by GitHub
parent 66d233134f
commit 88bfc32dfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 137 additions and 98 deletions

View File

@ -6294,6 +6294,22 @@ mod tests {
");
}
#[test]
fn test_literal_type_alias_inlay_hint() {
let mut test = inlay_hint_test(
"
from typing import Literal
a = Literal['a', 'b', 'c']",
);
assert_snapshot!(test.inlay_hints(), @r"
from typing import Literal
a[: <typing.Literal special form>] = Literal['a', 'b', 'c']
");
}
struct InlayHintLocationDiagnostic {
source: FileRange,
target: FileRange,

View File

@ -51,7 +51,6 @@ use crate::types::constraints::{
};
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
use crate::types::display::TupleSpecialization;
pub use crate::types::display::{DisplaySettings, TypeDetail, TypeDisplayDetails};
use crate::types::enums::{enum_metadata, is_single_member_enum};
use crate::types::function::{
@ -8233,97 +8232,7 @@ impl<'db> KnownInstanceType<'db> {
/// Return the repr of the symbol at runtime
fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
struct KnownInstanceRepr<'db> {
known_instance: KnownInstanceType<'db>,
db: &'db dyn Db,
}
impl std::fmt::Display for KnownInstanceRepr<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.known_instance {
KnownInstanceType::SubscriptedProtocol(generic_context) => {
f.write_str("typing.Protocol")?;
generic_context.display(self.db).fmt(f)
}
KnownInstanceType::SubscriptedGeneric(generic_context) => {
f.write_str("typing.Generic")?;
generic_context.display(self.db).fmt(f)
}
KnownInstanceType::TypeAliasType(alias) => {
if let Some(specialization) = alias.specialization(self.db) {
f.write_str(alias.name(self.db))?;
specialization
.display_short(
self.db,
TupleSpecialization::No,
DisplaySettings::default(),
)
.fmt(f)
} else {
f.write_str("typing.TypeAliasType")
}
}
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
KnownInstanceType::TypeVar(typevar_instance) => {
if typevar_instance.kind(self.db).is_paramspec() {
f.write_str("typing.ParamSpec")
} else {
f.write_str("typing.TypeVar")
}
}
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
KnownInstanceType::Field(field) => {
f.write_str("dataclasses.Field")?;
if let Some(default_ty) = field.default_type(self.db) {
write!(f, "[{}]", default_ty.display(self.db))?;
}
Ok(())
}
KnownInstanceType::ConstraintSet(tracked_set) => {
let constraints = tracked_set.constraints(self.db);
write!(
f,
"ty_extensions.ConstraintSet[{}]",
constraints.display(self.db)
)
}
KnownInstanceType::GenericContext(generic_context) => {
write!(
f,
"ty_extensions.GenericContext{}",
generic_context.display_full(self.db)
)
}
KnownInstanceType::Specialization(specialization) => {
// Normalize for consistent output across CI platforms
write!(
f,
"ty_extensions.Specialization{}",
specialization.display_full(self.db)
)
}
KnownInstanceType::UnionType(_) => f.write_str("types.UnionType"),
KnownInstanceType::Literal(_) => f.write_str("<typing.Literal special form>"),
KnownInstanceType::Annotated(_) => {
f.write_str("<typing.Annotated special form>")
}
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
f.write_str("GenericAlias")
}
KnownInstanceType::LiteralStringAlias(_) => f.write_str("str"),
KnownInstanceType::NewType(declaration) => {
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
}
}
}
}
KnownInstanceRepr {
known_instance: self,
db,
}
self.display_with(db, DisplaySettings::default())
}
}
@ -8368,6 +8277,10 @@ impl DynamicType<'_> {
Self::Any
}
}
pub(crate) fn is_todo(&self) -> bool {
matches!(self, Self::Todo(_) | Self::TodoUnpack)
}
}
impl std::fmt::Display for DynamicType<'_> {

View File

@ -22,8 +22,8 @@ use crate::types::tuple::TupleSpec;
use crate::types::visitor::TypeVisitor;
use crate::types::{
BoundTypeVarIdentity, CallableType, IntersectionType, KnownBoundMethodType, KnownClass,
MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType,
SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor,
KnownInstanceType, MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType,
StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor,
};
/// Settings for displaying types and signatures
@ -582,7 +582,12 @@ 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) => write!(f.with_type(self.ty), "{dynamic}"),
Type::Dynamic(dynamic) => {
if dynamic.is_todo() {
f.set_invalid_syntax();
}
write!(f.with_type(self.ty), "{dynamic}")
}
Type::Never => f.with_type(self.ty).write_str("Never"),
Type::NominalInstance(instance) => {
let class = instance.class(self.db);
@ -687,9 +692,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
Type::SpecialForm(special_form) => {
write!(f.with_type(self.ty), "{special_form}")
}
Type::KnownInstance(known_instance) => {
write!(f.with_type(self.ty), "{}", known_instance.repr(self.db))
}
Type::KnownInstance(known_instance) => known_instance
.display_with(self.db, self.settings.clone())
.fmt_detailed(f),
Type::FunctionLiteral(function) => function
.display_with(self.db, self.settings.clone())
.fmt_detailed(f),
@ -2133,6 +2138,111 @@ impl Display for DisplayStringLiteralType<'_> {
}
}
pub(crate) struct DisplayKnownInstanceRepr<'db> {
pub(crate) known_instance: KnownInstanceType<'db>,
pub(crate) db: &'db dyn Db,
}
impl<'db> KnownInstanceType<'db> {
pub(crate) fn display_with(
self,
db: &'db dyn Db,
_settings: DisplaySettings<'db>,
) -> DisplayKnownInstanceRepr<'db> {
DisplayKnownInstanceRepr {
known_instance: self,
db,
}
}
}
impl Display for DisplayKnownInstanceRepr<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.fmt_detailed(&mut TypeWriter::Formatter(f))
}
}
impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
let ty = Type::KnownInstance(self.known_instance);
match self.known_instance {
KnownInstanceType::SubscriptedProtocol(generic_context) => {
f.with_type(ty).write_str("typing.Protocol")?;
f.write_str(&generic_context.display(self.db).to_string())
}
KnownInstanceType::SubscriptedGeneric(generic_context) => {
f.with_type(ty).write_str("typing.Generic")?;
f.write_str(&generic_context.display(self.db).to_string())
}
KnownInstanceType::TypeAliasType(alias) => {
if let Some(specialization) = alias.specialization(self.db) {
f.write_str(alias.name(self.db))?;
f.write_str(
&specialization
.display_short(
self.db,
TupleSpecialization::No,
DisplaySettings::default(),
)
.to_string(),
)
} else {
f.with_type(ty).write_str("typing.TypeAliasType")
}
}
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
KnownInstanceType::TypeVar(typevar_instance) => {
if typevar_instance.kind(self.db).is_paramspec() {
f.write_str("typing.ParamSpec")
} else {
f.write_str("typing.TypeVar")
}
}
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
KnownInstanceType::Field(field) => {
f.with_type(ty).write_str("dataclasses.Field")?;
if let Some(default_ty) = field.default_type(self.db) {
write!(f, "[{}]", default_ty.display(self.db))?;
}
Ok(())
}
KnownInstanceType::ConstraintSet(tracked_set) => {
let constraints = tracked_set.constraints(self.db);
f.with_type(ty).write_str("ty_extensions.ConstraintSet")?;
write!(f, "[{}]", constraints.display(self.db))
}
KnownInstanceType::GenericContext(generic_context) => {
f.with_type(ty).write_str("ty_extensions.GenericContext")?;
write!(f, "{}", generic_context.display_full(self.db))
}
KnownInstanceType::Specialization(specialization) => {
// Normalize for consistent output across CI platforms
f.with_type(ty).write_str("ty_extensions.Specialization")?;
write!(f, "{}", specialization.display_full(self.db))
}
KnownInstanceType::UnionType(_) => f.with_type(ty).write_str("types.UnionType"),
KnownInstanceType::Literal(_) => {
f.set_invalid_syntax();
f.write_str("<typing.Literal special form>")
}
KnownInstanceType::Annotated(_) => {
f.set_invalid_syntax();
f.write_str("<typing.Annotated special form>")
}
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
f.with_type(ty).write_str("GenericAlias")
}
KnownInstanceType::LiteralStringAlias(_) => f.write_str("str"),
KnownInstanceType::NewType(declaration) => {
f.set_invalid_syntax();
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
}
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;