Skip to content
Merged
63 changes: 63 additions & 0 deletions spec-draft.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

Grammar specification of the extensions to the type language.

It's important that there be a clearly specified type language for the type-level computation---we can't just be using some poorly specified subset of all Python.


::

<type> = ...
| <type> if <type-bool> else <type>

# Create NewProtocols and Unions using for loops.
# They can take either a single list comprehension as an
# argument, or starred list comprehensions can be included
# in the argument list.

# TODO: NewProtocol needs a way of doing bases also...
# TODO: Should probably support Callable, TypedDict, etc
| NewProtocol[<type-for(<prop-spec>)>]
| NewProtocol[<variadic-type-arg(<prop-spec>)> +]

| Union[<type-for(<type>)>]
| Union[<variadic-type-arg(<type-for>)> +]

| GetAttr[<type>, <type>]
| GetArg[<type>, <int-literal>]

# String manipulation operations for string Literal types.
# We can put more in, but this is what typescript has.
| Uppercase[<type>] | Lowercase[<type>]
| Capitalize[<type>] | Uncapitalize[<type>]

# Type conditional checks are just boolean compositions of
# subtype checking.
<type-bool> =
IsSubtype[<type>, <type>]
| not <type-bool>
| <type-bool> and <type-bool>
| <type-bool> or <type-bool>
# Do we want these next two?
| any(<type-for(<type-bool>)>)
| all(<type-for(<type-bool>)>)

<prop-spec> = Property[<type>, <type>]

<variadic-type-arg(T)> =
T ,
| * <type-for-iter(T)> ,


<type-for(T)> = [ T <type-for-iter>+ <type-for-if>* ]
<type-for-iter> =
for <var> in IterUnion<type>
| for <var>, <var> in DirProperties<type>
# TODO: callspecs
# TODO: variadic args (tuples, callables)
<type-for-if> =
if <type-bool>



``type-for(T)`` and ``variadic-type-arg(T)`` are parameterized grammar
rules, which can take different
2 changes: 1 addition & 1 deletion tests/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def func[C: next.CallSpec](
*args: C.args, **kwargs: C.kwargs
) -> next.NewProtocol[
[next.Property[c.name, int] for c in next.CallSpecKwargs[C]]
*[next.Property[c.name, int] for c in next.CallSpecKwargs[C]]
]: ...


Expand Down
112 changes: 112 additions & 0 deletions tests/test_qblike.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import textwrap

from typemap.type_eval import eval_call, eval_typing
from typemap import typing as next

from . import format_helper


class Property[T]:
pass


class Link[T]:
pass


type PropsOnly[T] = next.NewProtocol[
[
next.Property[p.name, p.type]
for p in next.DirProperties[T]
if next.IsSubtype[p.type, Property]
]
]

# Conditional type alias!
type FilterLinks[T] = (
Link[PropsOnly[next.GetArg[T, 0]]] if next.IsSubtype[T, Link] else T
)


# Basic filtering
class Tgt2:
pass


class Tgt:
name: Property[str]
tgt2: Link[Tgt2]


class A:
x: Property[int]
y: Property[bool | None]
z: Link[Tgt]
w: Property[list[str]]


def select[C: next.CallSpec](
__rcv: A, *args: C.args, **kwargs: C.kwargs
) -> next.NewProtocol[
[
next.Property[
c.name,
FilterLinks[next.GetAttr[A, c.name]],
]
for c in next.CallSpecKwargs[C]
]
]: ...


def test_qblike_1():
ret = eval_call(
select,
A(),
x=True,
w=True,
)
fmt = format_helper.format_class(ret)

assert fmt == textwrap.dedent("""\
class select[...]:
x: tests.test_qblike.Property[int]
w: tests.test_qblike.Property[list[str]]
""")


def test_qblike_2():
ret = eval_typing(PropsOnly[A])
fmt = format_helper.format_class(ret)

assert fmt == textwrap.dedent("""\
class PropsOnly[tests.test_qblike.A]:
x: tests.test_qblike.Property[int]
y: tests.test_qblike.Property[bool | None]
w: tests.test_qblike.Property[list[str]]
""")


def test_qblike_3():
ret = eval_call(
select,
A(),
x=True,
w=True,
z=True,
)
fmt = format_helper.format_class(ret)

assert fmt == textwrap.dedent("""\
class select[...]:
x: tests.test_qblike.Property[int]
w: tests.test_qblike.Property[list[str]]
z: tests.test_qblike.Link[PropsOnly[tests.test_qblike.Tgt]]
""")

tgt = eval_typing(next.GetAttr[ret, "z"].__args__[0])
fmt = format_helper.format_class(tgt)

assert fmt == textwrap.dedent("""\
class PropsOnly[tests.test_qblike.Tgt]:
name: tests.test_qblike.Property[str]
""")
75 changes: 74 additions & 1 deletion tests/test_type_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,47 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]):


type AllOptional[T] = next.NewProtocol[
[next.Property[p.name, p.type | None] for p in next.DirProperties[T]]
*[next.Property[p.name, p.type | None] for p in next.DirProperties[T]]
]

type OptionalFinal = AllOptional[Final]


type Capitalize[T] = next.NewProtocol[
[
next.Property[next.Uppercase[p.name], p.type]
for p in next.DirProperties[T]
]
]

type Prims[T] = next.NewProtocol[
*[
next.Property[name, typ]
for name, typ in next.DirProperties[T]
if next.IsSubtype[typ, int | str]
]
]


type NoLiterals[T] = next.NewProtocol[
*[
next.Property[
p.name,
typing.Union[
*[
t
for t in next.IterUnion[p.type]
# XXX: 'typing.Literal' is not *really* a type...
# Maybe we can't do this, which maybe is fine.
if not next.IsSubtype[t, typing.Literal]
]
],
]
for p in next.DirProperties[T]
]
]


def test_type_dir_1():
d = eval_typing(Final)

Expand Down Expand Up @@ -92,3 +127,41 @@ class AllOptional[tests.test_type_dir.Final]:
x: tests.test_type_dir.Wrapper[int | None] | None
ordinary: str | None
""")


def test_type_dir_3():
d = eval_typing(Capitalize[Final])

assert format_helper.format_class(d) == textwrap.dedent("""\
class Capitalize[tests.test_type_dir.Final]:
LAST: int | typing.Literal[True]
III: str | int | typing.Literal['gotcha!']
T: dict[str, str | int | typing.Literal['gotcha!']]
KKK: ~K
X: tests.test_type_dir.Wrapper[int | None]
ORDINARY: str
""")


def test_type_dir_4():
d = eval_typing(Prims[Final])

assert format_helper.format_class(d) == textwrap.dedent("""\
class Prims[tests.test_type_dir.Final]:
last: int | typing.Literal[True]
ordinary: str
""")


def test_type_dir_5():
d = eval_typing(NoLiterals[Final])

assert format_helper.format_class(d) == textwrap.dedent("""\
class NoLiterals[tests.test_type_dir.Final]:
last: int
iii: str | int
t: dict[str, str | int | typing.Literal['gotcha!']]
kkk: ~K
x: tests.test_type_dir.Wrapper[int | None]
ordinary: str
""")
22 changes: 18 additions & 4 deletions tests/test_type_eval.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import textwrap
import typing
import unittest

from typemap import typing as next
from typemap.type_eval import eval_typing
Expand All @@ -22,15 +23,16 @@ class F_int(F[int]):


type MapRecursive[A] = next.NewProtocol[
[
*[
(
next.Property[p.name, OrGotcha[p.type]]
if p.type is not A
if not next.IsSubtype[p.type, A]
else next.Property[p.name, OrGotcha[MapRecursive[A]]]
)
# XXX: type language - concatenating DirProperties is sketchy
for p in (next.DirProperties[A] + next.DirProperties[F_int])
]
+ [next.Property["control", float]] # noqa: F821
],
next.Property[typing.Literal["control"], float],
]


Expand Down Expand Up @@ -60,3 +62,15 @@ class MapRecursive[tests.test_type_eval.Recursive]:
fff: int | typing.Literal['gotcha!']
control: float
""")


# XXX: should this work???
# probably not?
@unittest.skip
def test_eval_types_3():
evaled = eval_typing(F[bool])

assert format_helper.format_class(evaled) == textwrap.dedent("""\
class F[bool]:
fff: bool
""")
4 changes: 2 additions & 2 deletions typemap/type_eval/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ._eval_call import eval_call
from ._eval_typing import eval_typing, _get_current_context
from ._subtype import issubtype


__all__ = ("eval_typing", "eval_call", "_get_current_context")
1
__all__ = ("eval_typing", "eval_call", "issubtype", "_get_current_context")
Loading