diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_api.md b/crates/red_knot_python_semantic/resources/mdtest/type_api.md index c4e7aa1a16..715ceef584 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_api.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_api.md @@ -94,6 +94,36 @@ reveal_type(C.__mro__) u: Unknown[str] ``` +### `AlwaysTruthy` and `AlwaysFalsy` + +`AlwaysTruthy` and `AlwaysFalsy` represent the sets of all possible objects whose truthiness is +always truthy or falsy, respectively. + +They do not accept any type arguments. + +```py +from typing_extensions import Literal + +from knot_extensions import AlwaysFalsy, AlwaysTruthy, is_subtype_of, static_assert + +static_assert(is_subtype_of(Literal[True], AlwaysTruthy)) +static_assert(is_subtype_of(Literal[False], AlwaysFalsy)) + +static_assert(not is_subtype_of(int, AlwaysFalsy)) +static_assert(not is_subtype_of(str, AlwaysFalsy)) + +def _(t: AlwaysTruthy, f: AlwaysFalsy): + reveal_type(t) # revealed: AlwaysTruthy + reveal_type(f) # revealed: AlwaysFalsy + +def f( + a: AlwaysTruthy[int], # error: [invalid-type-form] + b: AlwaysFalsy[str], # error: [invalid-type-form] +): + reveal_type(a) # revealed: Unknown + reveal_type(b) # revealed: Unknown +``` + ## Static assertions ### Basics diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 9c22c45ae3..3bec7b5e46 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2292,6 +2292,8 @@ impl<'db> Type<'db> { fallback_type: Type::unknown(), }), Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()), + Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy), + Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy), _ => Ok(todo_type!( "Unsupported or invalid type in a type expression" )), @@ -2827,6 +2829,10 @@ pub enum KnownInstanceType<'db> { TypeAliasType(TypeAliasType<'db>), /// The symbol `knot_extensions.Unknown` Unknown, + /// The symbol `knot_extensions.AlwaysTruthy` + AlwaysTruthy, + /// The symbol `knot_extensions.AlwaysFalsy` + AlwaysFalsy, /// The symbol `knot_extensions.Not` Not, /// The symbol `knot_extensions.Intersection` @@ -2888,6 +2894,8 @@ impl<'db> KnownInstanceType<'db> { Self::OrderedDict => "OrderedDict", Self::ReadOnly => "ReadOnly", Self::Unknown => "Unknown", + Self::AlwaysTruthy => "AlwaysTruthy", + Self::AlwaysFalsy => "AlwaysFalsy", Self::Not => "Not", Self::Intersection => "Intersection", Self::TypeOf => "TypeOf", @@ -2931,6 +2939,8 @@ impl<'db> KnownInstanceType<'db> { | Self::ReadOnly | Self::TypeAliasType(_) | Self::Unknown + | Self::AlwaysTruthy + | Self::AlwaysFalsy | Self::Not | Self::Intersection | Self::TypeOf => Truthiness::AlwaysTrue, @@ -2974,6 +2984,8 @@ impl<'db> KnownInstanceType<'db> { Self::TypeVar(typevar) => typevar.name(db), Self::TypeAliasType(_) => "typing.TypeAliasType", Self::Unknown => "knot_extensions.Unknown", + Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy", + Self::AlwaysFalsy => "knot_extensions.AlwaysFalsy", Self::Not => "knot_extensions.Not", Self::Intersection => "knot_extensions.Intersection", Self::TypeOf => "knot_extensions.TypeOf", @@ -3020,6 +3032,8 @@ impl<'db> KnownInstanceType<'db> { Self::Not => KnownClass::SpecialForm, Self::Intersection => KnownClass::SpecialForm, Self::Unknown => KnownClass::Object, + Self::AlwaysTruthy => KnownClass::Object, + Self::AlwaysFalsy => KnownClass::Object, } } @@ -3071,6 +3085,8 @@ impl<'db> KnownInstanceType<'db> { "NotRequired" => Self::NotRequired, "LiteralString" => Self::LiteralString, "Unknown" => Self::Unknown, + "AlwaysTruthy" => Self::AlwaysTruthy, + "AlwaysFalsy" => Self::AlwaysFalsy, "Not" => Self::Not, "Intersection" => Self::Intersection, "TypeOf" => Self::TypeOf, @@ -3123,9 +3139,12 @@ impl<'db> KnownInstanceType<'db> { | Self::TypeVar(_) => { matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) } - Self::Unknown | Self::Not | Self::Intersection | Self::TypeOf => { - module.is_knot_extensions() - } + Self::Unknown + | Self::AlwaysTruthy + | Self::AlwaysFalsy + | Self::Not + | Self::Intersection + | Self::TypeOf => module.is_knot_extensions(), } } diff --git a/crates/red_knot_python_semantic/src/types/class_base.rs b/crates/red_knot_python_semantic/src/types/class_base.rs index 1b4a357914..18f0605b0d 100644 --- a/crates/red_knot_python_semantic/src/types/class_base.rs +++ b/crates/red_knot_python_semantic/src/types/class_base.rs @@ -105,7 +105,9 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::Optional | KnownInstanceType::Not | KnownInstanceType::Intersection - | KnownInstanceType::TypeOf => None, + | KnownInstanceType::TypeOf + | KnownInstanceType::AlwaysTruthy + | KnownInstanceType::AlwaysFalsy => None, KnownInstanceType::Unknown => Some(Self::unknown()), KnownInstanceType::Any => Some(Self::any()), // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 4923285adb..194ce6c634 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4539,7 +4539,7 @@ impl<'db> TypeInferenceBuilder<'db> { return dunder_getitem_method .call(self.db(), &CallArguments::positional([value_ty, slice_ty])) - .return_ty_result( &self.context, value_node.into()) + .return_ty_result(&self.context, value_node.into()) .unwrap_or_else(|err| { self.context.report_lint( &CALL_NON_CALLABLE, @@ -5373,7 +5373,11 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_type_expression(arguments_slice); todo_type!("`Unpack[]` special form") } - KnownInstanceType::NoReturn | KnownInstanceType::Never | KnownInstanceType::Any => { + KnownInstanceType::NoReturn + | KnownInstanceType::Never + | KnownInstanceType::Any + | KnownInstanceType::AlwaysTruthy + | KnownInstanceType::AlwaysFalsy => { self.context.report_lint( &INVALID_TYPE_FORM, subscript.into(), diff --git a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi index 581b2a3d29..e2695caa97 100644 --- a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi +++ b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi @@ -5,6 +5,8 @@ def static_assert(condition: object, msg: LiteralString | None = None) -> None: # Types Unknown = object() +AlwaysTruthy = object() +AlwaysFalsy = object() # Special forms Not: _SpecialForm