Skip to content

Commit

Permalink
[red-knot] Add AlwaysTruthy and AlwaysFalsy to knot_extensions (#…
Browse files Browse the repository at this point in the history
…15437)

Co-authored-by: Alex Waygood <[email protected]>
  • Loading branch information
InSyncWithFoo and AlexWaygood authored Jan 12, 2025
1 parent 06b7f44 commit d1666fb
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 6 deletions.
30 changes: 30 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/type_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 22 additions & 3 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)),
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
}
}

Expand Down
4 changes: 3 additions & 1 deletion crates/red_knot_python_semantic/src/types/class_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 2 additions & 0 deletions crates/red_knot_vendored/knot_extensions/knot_extensions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d1666fb

Please sign in to comment.