[ty] Literal promotion refactor (#20646)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

Not sure if this was the original intention, but it looks to me like the
previous `Type::literal_promotion_type` was more of an implementation
detail for the actual operation of promoting all literals in a
possibly-nested position of a type.

This is not a pure refactor, as I'm technically changing the behavior
for that protocols diagnostic message suggestion.

## Test Plan

New Markdown test
This commit is contained in:
David Peter 2025-09-30 14:22:36 +02:00 committed by GitHub
parent 130a794c2b
commit b483d3b0b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 21 deletions

View file

@ -893,8 +893,10 @@ class LotsOfBindings(Protocol):
match object(): match object():
case l: # error: [ambiguous-protocol-member] case l: # error: [ambiguous-protocol-member]
... ...
# error: [ambiguous-protocol-member] "Consider adding an annotation, e.g. `m: int | str = ...`"
m = 1 if 1.2 > 3.4 else "a"
# revealed: frozenset[Literal["Nested", "NestedProtocol", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]] # revealed: frozenset[Literal["Nested", "NestedProtocol", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"]]
reveal_type(get_protocol_members(LotsOfBindings)) reveal_type(get_protocol_members(LotsOfBindings))
class Foo(Protocol): class Foo(Protocol):

View file

@ -1166,21 +1166,26 @@ impl<'db> Type<'db> {
} }
} }
/// If this type is a literal, promote it to a type that this literal is an instance of. /// Promote (possibly nested) literals to types that these literals are instances of.
/// ///
/// Note that this function tries to promote literals to a more user-friendly form than their /// Note that this function tries to promote literals to a more user-friendly form than their
/// fallback instance type. For example, `def _() -> int` is promoted to `Callable[[], int]`, /// fallback instance type. For example, `def _() -> int` is promoted to `Callable[[], int]`,
/// as opposed to `FunctionType`. /// as opposed to `FunctionType`.
pub(crate) fn literal_promotion_type(self, db: &'db dyn Db) -> Option<Type<'db>> { pub(crate) fn promote_literals(self, db: &'db dyn Db) -> Type<'db> {
self.apply_type_mapping(db, &TypeMapping::PromoteLiterals)
}
/// Like [`Type::promote_literals`], but does not recurse into nested types.
fn promote_literals_impl(self, db: &'db dyn Db) -> Type<'db> {
match self { match self {
Type::StringLiteral(_) | Type::LiteralString => Some(KnownClass::Str.to_instance(db)), Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_instance(db),
Type::BooleanLiteral(_) => Some(KnownClass::Bool.to_instance(db)), Type::BooleanLiteral(_) => KnownClass::Bool.to_instance(db),
Type::IntLiteral(_) => Some(KnownClass::Int.to_instance(db)), Type::IntLiteral(_) => KnownClass::Int.to_instance(db),
Type::BytesLiteral(_) => Some(KnownClass::Bytes.to_instance(db)), Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db),
Type::ModuleLiteral(_) => Some(KnownClass::ModuleType.to_instance(db)), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_instance(db),
Type::EnumLiteral(literal) => Some(literal.enum_class_instance(db)), Type::EnumLiteral(literal) => literal.enum_class_instance(db),
Type::FunctionLiteral(literal) => Some(Type::Callable(literal.into_callable_type(db))), Type::FunctionLiteral(literal) => Type::Callable(literal.into_callable_type(db)),
_ => None, _ => self,
} }
} }
@ -6080,8 +6085,7 @@ impl<'db> Type<'db> {
let function = Type::FunctionLiteral(function.apply_type_mapping_impl(db, type_mapping, visitor)); let function = Type::FunctionLiteral(function.apply_type_mapping_impl(db, type_mapping, visitor));
match type_mapping { match type_mapping {
TypeMapping::PromoteLiterals => function.literal_promotion_type(db) TypeMapping::PromoteLiterals => function.promote_literals_impl(db),
.expect("function literal should have a promotion type"),
_ => function _ => function
} }
} }
@ -6189,8 +6193,7 @@ impl<'db> Type<'db> {
TypeMapping::ReplaceSelf { .. } | TypeMapping::ReplaceSelf { .. } |
TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::MarkTypeVarsInferable(_) |
TypeMapping::Materialize(_) => self, TypeMapping::Materialize(_) => self,
TypeMapping::PromoteLiterals => self.literal_promotion_type(db) TypeMapping::PromoteLiterals => self.promote_literals_impl(db)
.expect("literal type should have a promotion type"),
} }
Type::Dynamic(_) => match type_mapping { Type::Dynamic(_) => match type_mapping {

View file

@ -2625,6 +2625,12 @@ pub(crate) fn report_undeclared_protocol_member(
SubclassOfInner::Dynamic(_) => return false, SubclassOfInner::Dynamic(_) => return false,
}, },
Type::NominalInstance(instance) => instance.class(db), Type::NominalInstance(instance) => instance.class(db),
Type::Union(union) => {
return union
.elements(db)
.iter()
.all(|elem| should_give_hint(db, *elem));
}
_ => return false, _ => return false,
}; };
@ -2656,9 +2662,7 @@ pub(crate) fn report_undeclared_protocol_member(
if definition.kind(db).is_unannotated_assignment() { if definition.kind(db).is_unannotated_assignment() {
let binding_type = binding_type(db, definition); let binding_type = binding_type(db, definition);
let suggestion = binding_type let suggestion = binding_type.promote_literals(db);
.literal_promotion_type(db)
.unwrap_or(binding_type);
if should_give_hint(db, suggestion) { if should_give_hint(db, suggestion) {
diagnostic.set_primary_message(format_args!( diagnostic.set_primary_message(format_args!(

View file

@ -92,7 +92,7 @@ use crate::types::{
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeMapping, TypeQualifiers, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind,
UnionBuilder, UnionType, binding_type, todo_type, UnionBuilder, UnionType, binding_type, todo_type,
}; };
@ -5432,8 +5432,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Convert any element literals to their promoted type form to avoid excessively large // Convert any element literals to their promoted type form to avoid excessively large
// unions for large nested list literals, which the constraint solver struggles with. // unions for large nested list literals, which the constraint solver struggles with.
let inferred_elt_ty = let inferred_elt_ty = inferred_elt_ty.promote_literals(self.db());
inferred_elt_ty.apply_type_mapping(self.db(), &TypeMapping::PromoteLiterals);
builder builder
.infer(Type::TypeVar(*elt_ty), inferred_elt_ty) .infer(Type::TypeVar(*elt_ty), inferred_elt_ty)