Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
KotlinIsland committed Sep 8, 2024
1 parent 2dd29c9 commit af5083e
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- run: ./pw poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
id: cache
with:
path: .venv
Expand All @@ -47,7 +47,7 @@ jobs:
python-version: "3.9"
- run: ./pw poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
id: cache
with:
path: .venv
Expand Down
43 changes: 36 additions & 7 deletions src/basedtyping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
_remove_dups_flatten,
_SpecialForm,
_tp_cache,
_type_check,
cast,
)

Expand All @@ -33,6 +32,7 @@
from basedtyping import transformer
from basedtyping.runtime_only import OldUnionType

BASEDMYPY_TYPE_CHECKING = False
if not TYPE_CHECKING:
if sys.version_info >= (3, 11):
from typing import _collect_parameters
Expand All @@ -57,6 +57,7 @@
"TypeForm",
"as_functiontype",
"ForwardRef",
"BASEDMYPY_TYPE_CHECKING",
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -453,11 +454,7 @@ def issubform(form: _Forms, forminfo: _Forms) -> bool:
return issubclass(form, forminfo) # type: ignore[arg-type]


if TYPE_CHECKING:
# We pretend that it's an alias to Any so that it's slightly more compatible with
# other tools, basedmypy will still utilize the SpecialForm over the TypeAlias.
Untyped: TypeAlias = Any # type: ignore[no-any-explicit]
else:
if BASEDMYPY_TYPE_CHECKING or not TYPE_CHECKING:

@_BasedSpecialForm
def Untyped( # noqa: N802
Expand All @@ -469,6 +466,10 @@ def Untyped( # noqa: N802
This is more specialized than ``Any`` and can help with gradually typing modules.
"""
raise TypeError(f"{self} is not subscriptable")
else:
# We pretend that it's an alias to Any so that it's slightly more compatible with
# other tools
Untyped: TypeAlias = Any # type: ignore[no-any-explicit]


class _IntersectionGenericAlias(_BasedGenericAlias, _root=True):
Expand Down Expand Up @@ -529,7 +530,7 @@ def Intersection(self: _BasedSpecialForm, parameters: object) -> object: # noqa
if not isinstance(parameters, tuple):
parameters = (parameters,)
msg = "Intersection[arg, ...]: each arg must be a type."
parameters = tuple(_type_check(p, msg) for p in parameters) # type: ignore[no-any-expr]
parameters = tuple(_type_check(p, msg) for p in parameters)
parameters = _remove_dups_flatten(parameters) # type: ignore[no-any-expr]
if len(parameters) == 1: # type: ignore[no-any-expr]
return parameters[0] # type: ignore[no-any-expr]
Expand Down Expand Up @@ -660,3 +661,31 @@ def _evaluate(
recursive_guard: frozenset[str], # noqa: ARG002
) -> object | None:
return transformer._eval_direct(self, globalns, localns)


def _type_check(arg: object, msg: str) -> object:
"""Check that the argument is a type, and return it (internal helper).
As a special case, accept None and return type(None) instead. Also wrap strings
into ForwardRef instances. Consider several corner cases, for example plain
special forms like Union are not valid, while Union[int, str] is OK, etc.
The msg argument is a human-readable error message, e.g::
"Union[arg, ...]: arg should be a type."
We append the repr() of the actual value (truncated to 100 chars).
"""
invalid_generic_forms = (Generic, typing.Protocol)

arg = _type_convert(arg)
if isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms: # type: ignore[comparison-overlap]
raise TypeError(f"{arg} is not valid as type argument")
if arg in (Any, NoReturn, typing.Final, Untyped):
return arg
if isinstance(arg, _SpecialForm) or arg in (Generic, typing.Protocol):
raise TypeError(f"Plain {arg} is not valid as type argument")
if isinstance(arg, (type, TypeVar, ForwardRef)):
return arg
if not callable(arg):
raise TypeError(f"{msg} Got {arg!r:.100}.")
return arg
16 changes: 2 additions & 14 deletions tests/test_basedspecialform.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from __future__ import annotations

import sys

import pytest
from basedtyping import Intersection, TypeForm, Untyped


Expand All @@ -11,15 +8,6 @@ def test_basedgenericalias_intersection():
assert None & TypeForm[int] == Intersection[None, TypeForm[int]]


@pytest.mark.xfail(
sys.version_info >= (3, 9),
reason="""
`typing._type_check` says:
if arg in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
return arg
""",
)
def test_basedspecialform_intersection():
assert Untyped & None == Intersection[Untyped, None] # type: ignore[no-any-expr]
assert None & Untyped == Intersection[Untyped, None] # type: ignore[no-any-expr]
assert Untyped & None == Intersection[Untyped, None]
assert None & Untyped == Intersection[Untyped, None]

0 comments on commit af5083e

Please sign in to comment.