Add Bool special form to handle type expressions in boolean contexts#31
Add Bool special form to handle type expressions in boolean contexts#31
Conversation
|
I don't think this is what I was thinking; I don't really want to have a separate "kind" ("sort"?) of boolean type alias. What I was thinking was maybe we have an abbreviation for I'm not sure how much it is needed. TypeScript doesn't have anything but subtype check for this |
typemap/type_eval/_eval_operators.py
Outdated
| @type_eval.register_evaluator(Bool) | ||
| def _eval_Bool(tp, *, ctx): | ||
| result = _eval_types(tp, ctx) | ||
| if isinstance(result, int) and not isinstance(result, bool): | ||
| # Python type computations can sometimes return int instead of bool. | ||
| # For example, this happens if a type alias is an any/all expression. | ||
| return bool(result) | ||
| return result | ||
|
|
There was a problem hiding this comment.
This should never trigger, right?
There was a problem hiding this comment.
Oh nevermind, that is fully wrong
typemap/type_eval/_special_form.py
Outdated
| result = evaluator(self) | ||
| if isinstance(result, bool): | ||
| return result | ||
| elif isinstance(result, int): | ||
| # Python type computations can sometimes return int instead of | ||
| # bool. See: _eval_Bool | ||
| return bool(result) | ||
| elif ( | ||
| hasattr(result, '__origin__') | ||
| and result.__origin__ is typing.Literal | ||
| ): | ||
| return typing.get_args(result)[0] | ||
|
|
||
| raise RuntimeError(f"Expected bool or Literal[bool], got {result}") |
There was a problem hiding this comment.
I think all this logic should be in _eval_Bool?
There was a problem hiding this comment.
I've simplified the whole thing so that all we do is "pass through" when in a type computation context and unpack literals when a bool context.
Tried a few things, but I don't think there's a reasonable way to prevent users from doing things like:
type JustFalse = Literal[False]
type ShouldBeStr = (int if JustFalse else str)We'll just have to document this well.
typemap/typing.py
Outdated
| @_SpecialForm | ||
| def Is(self, params): | ||
| """A boolean special form of IsSubSimilar.""" | ||
| return _BoolGenericAlias(self, params) |
There was a problem hiding this comment.
Could we return Bool[IsSubSimilar[*params]]?
There was a problem hiding this comment.
Is there some other shortened name we can give IsSubSimilar here, also...
There was a problem hiding this comment.
Removed my changes because you renamed Is to Sub.
Is there still a reason to remove the special form IsX classes? ie.
class IsSubtype[Child, Parent]:
pass
class IsSubSimilar[Child, Parent]:
passd194a31 to
3da742c
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
typemap/typing.py
Outdated
|
|
||
| _SpecialForm: typing.Any = typing._SpecialForm | ||
| from .type_eval._special_form import ( | ||
| _BoolGenericAlias, |
There was a problem hiding this comment.
We can't depend on type_eval from inside our typing.py. The idea is that these typing.py changes will go into the real typing.py, and so must not depend on the eval library, which will probably be third-party.
typing.special_form_evaluator in particular is part of the public API we are proposing, because it is how third-party eval libraries will hook in to handle bool/iter evals
typemap/type_eval/_special_form.py
Outdated
| return False | ||
|
|
||
|
|
||
| class _LiteralGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] |
There was a problem hiding this comment.
This should subtype the real typing._LiteralGenericAlias (and be named something else like _BooleanLiteralGeneric), so that it displays nicely
typemap/type_eval/_eval_operators.py
Outdated
| if _typing_inspect.is_generic_alias(tp): | ||
| if tp.__origin__ is typing.Literal: | ||
| return _LiteralGeneric[bool(tp.__args__[0])] | ||
| elif tp.__origin__ is _LiteralGeneric: | ||
| return _LiteralGeneric[bool(tp.__args__[0])] | ||
| raise TypeError(f"Expected Literal type, got {tp}") |
There was a problem hiding this comment.
My inclination is that this should be equivalent to IsSub[T, Literal[True]] and not IsSub[T, Never]
_LiteralGenericas the resulting type ofIsSuband other<type-bool>. This handles execution in boolean contexts.Matches[Lhs, Rhs]which is equivalent toIsSub[Lhs,Rhs] and IsSub[Rhs,Lhs]Boolwhich convertsLiteralto_LiteralGeneric(eg.Bool[Literal[False]]AllOf, which checks that all elements of a<variadic-type-arg> +are one ofLiteral[True]or_LiteralGeneric[True].AnyOf, which checks that any element of a<variadic-type-arg> +is one ofLiteral[True]or_LiteralGeneric[True].Example:
Notes:
AnyOfinstead ofAnybecause it would clash withtyping.Any._LiteralGeneric. This is becausetype NotBool[T] = not Bool[T]will always resolve to a bool._GlobalsWrapperwhich forces the immediate execution of any generics. This allows things like:This approach may allow us to entirely remove special forms for boolean types: #58