[ty] Add Top[] and Bottom[] special forms, replacing top_materialization_of() function (#20054)
Some checks failed
CI / mkdocs (push) Has been cancelled
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / cargo build (release) (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (windows) (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks-instrumented (push) Has been cancelled
CI / benchmarks-walltime (push) Has been cancelled

Part of astral-sh/ty#994

## Summary

Add new special forms to `ty_extensions`, `Top[T]` and `Bottom[T]`.
Remove `ty_extensions.top_materialization` and
`ty_extensions.bottom_materialization`.

## Test Plan

Converted the existing `materialization.md` mdtest to the new syntax.
Added some tests for invalid use of the new special form.
This commit is contained in:
Jelle Zijlstra 2025-08-23 11:20:56 -07:00 committed by GitHub
parent e7237652a9
commit ec86a4e960
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 288 additions and 182 deletions

View file

@ -4143,21 +4143,6 @@ impl<'db> Type<'db> {
.into()
}
Some(KnownFunction::TopMaterialization | KnownFunction::BottomMaterialization) => {
Binding::single(
self,
Signature::new(
Parameters::new([Parameter::positional_only(Some(Name::new_static(
"type",
)))
.type_form()
.with_annotated_type(Type::any())]),
Some(Type::any()),
),
)
.into()
}
Some(KnownFunction::AssertType) => Binding::single(
self,
Signature::new(
@ -5741,6 +5726,8 @@ impl<'db> Type<'db> {
SpecialFormType::Optional
| SpecialFormType::Not
| SpecialFormType::Top
| SpecialFormType::Bottom
| SpecialFormType::TypeOf
| SpecialFormType::TypeIs
| SpecialFormType::TypeGuard

View file

@ -726,18 +726,6 @@ impl<'db> Bindings<'db> {
}
}
Some(KnownFunction::TopMaterialization) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(ty.top_materialization(db));
}
}
Some(KnownFunction::BottomMaterialization) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(ty.bottom_materialization(db));
}
}
Some(KnownFunction::Len) => {
if let [Some(first_arg)] = overload.parameter_types() {
if let Some(len_ty) = first_arg.len(db) {

View file

@ -192,6 +192,8 @@ impl<'db> ClassBase<'db> {
| SpecialFormType::ReadOnly
| SpecialFormType::Optional
| SpecialFormType::Not
| SpecialFormType::Top
| SpecialFormType::Bottom
| SpecialFormType::Intersection
| SpecialFormType::TypeOf
| SpecialFormType::CallableTypeOf

View file

@ -1168,10 +1168,6 @@ pub enum KnownFunction {
AllMembers,
/// `ty_extensions.has_member`
HasMember,
/// `ty_extensions.top_materialization`
TopMaterialization,
/// `ty_extensions.bottom_materialization`
BottomMaterialization,
/// `ty_extensions.reveal_protocol_interface`
RevealProtocolInterface,
}
@ -1232,8 +1228,6 @@ impl KnownFunction {
| Self::IsSingleValued
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::TopMaterialization
| Self::BottomMaterialization
| Self::GenericContext
| Self::DunderAllNames
| Self::EnumMembers
@ -1569,8 +1563,6 @@ pub(crate) mod tests {
| KnownFunction::IsSingleValued
| KnownFunction::IsAssignableTo
| KnownFunction::IsEquivalentTo
| KnownFunction::TopMaterialization
| KnownFunction::BottomMaterialization
| KnownFunction::HasMember
| KnownFunction::RevealProtocolInterface
| KnownFunction::AllMembers => KnownModule::TyExtensions,

View file

@ -10599,6 +10599,54 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ty
}
SpecialFormType::Top => {
let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice {
&*tuple.elts
} else {
std::slice::from_ref(arguments_slice)
};
let num_arguments = arguments.len();
let arg = if num_arguments == 1 {
self.infer_type_expression(&arguments[0])
} else {
for argument in arguments {
self.infer_type_expression(argument);
}
report_invalid_argument_number_to_special_form(
&self.context,
subscript,
special_form,
num_arguments,
1,
);
Type::unknown()
};
arg.top_materialization(db)
}
SpecialFormType::Bottom => {
let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice {
&*tuple.elts
} else {
std::slice::from_ref(arguments_slice)
};
let num_arguments = arguments.len();
let arg = if num_arguments == 1 {
self.infer_type_expression(&arguments[0])
} else {
for argument in arguments {
self.infer_type_expression(argument);
}
report_invalid_argument_number_to_special_form(
&self.context,
subscript,
special_form,
num_arguments,
1,
);
Type::unknown()
};
arg.bottom_materialization(db)
}
SpecialFormType::TypeOf => {
let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice {
&*tuple.elts

View file

@ -77,6 +77,10 @@ pub enum SpecialFormType {
TypeOf,
/// The symbol `ty_extensions.CallableTypeOf`
CallableTypeOf,
/// The symbol `ty_extensions.Top`
Top,
/// The symbol `ty_extensions.Bottom`
Bottom,
/// The symbol `typing.Callable`
/// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`)
Callable,
@ -151,6 +155,8 @@ impl SpecialFormType {
| Self::TypeIs
| Self::TypeOf
| Self::Not
| Self::Top
| Self::Bottom
| Self::Intersection
| Self::CallableTypeOf
| Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says
@ -247,6 +253,8 @@ impl SpecialFormType {
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Top
| Self::Bottom
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf => module.is_ty_extensions(),
@ -291,6 +299,8 @@ impl SpecialFormType {
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Top
| Self::Bottom
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf
@ -352,6 +362,8 @@ impl SpecialFormType {
SpecialFormType::Intersection => "ty_extensions.Intersection",
SpecialFormType::TypeOf => "ty_extensions.TypeOf",
SpecialFormType::CallableTypeOf => "ty_extensions.CallableTypeOf",
SpecialFormType::Top => "ty_extensions.Top",
SpecialFormType::Bottom => "ty_extensions.Bottom",
SpecialFormType::Protocol => "typing.Protocol",
SpecialFormType::Generic => "typing.Generic",
SpecialFormType::NamedTuple => "typing.NamedTuple",