Skip to content

Commit

Permalink
atdpy: fix JSON representation of the option type (#333)
Browse files Browse the repository at this point in the history
* Disable incorrect interpretation of 'option' type in atdpy.

* Update changelog

* Add mostly-correct support for the option type in Python.
The JSON representation is now compatible with ATD's convention.
The Python representation, however, is still a nullable since Python
doesn't have a standard option type.
See #332

* Update documentation
  • Loading branch information
mjambon authored Mar 14, 2023
1 parent 797b988 commit fc3bcfb
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 35 deletions.
9 changes: 8 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
2.12.0 (xxxx-xx-xx)

* atdgen: Annotate generated code with types to disambiguate OCaml classic variants (#331)
* atdgen: Annotate generated code with types to disambiguate OCaml
classic variants (#331)
* atdpy: Support the option type more correctly so that it follows
ATD's convention for JSON encoding. This allows compatibility with
JSON produced by other tools of the ATD suite. The Python type,
however, is still a nullable (`Optional`) to make things simpler for
Python programmers. This prevents distinguishing `["Some", "None"]`
from `"None"` which both translate to `None` in Python. (#332)

2.11.0 (2023-02-08)
-------------------
Expand Down
29 changes: 27 additions & 2 deletions atdpy/src/lib/Codegen.ml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,19 @@ def _atd_read_nullable(read_elt: Callable[[Any], Any]) \
return read_nullable


def _atd_read_option(read_elt: Callable[[Any], Any]) \
-> Callable[[Optional[Any]], Optional[Any]]:
def read_option(x: Any) -> Any:
if x == 'None':
return None
elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some':
return read_elt(x[1])
else:
_atd_bad_json('option', x)
raise AssertionError('impossible') # keep mypy happy
return read_option


def _atd_write_unit(x: Any) -> None:
if x is None:
return x
Expand Down Expand Up @@ -427,6 +440,16 @@ def _atd_write_nullable(write_elt: Callable[[Any], Any]) \
return write_nullable


def _atd_write_option(write_elt: Callable[[Any], Any]) \
-> Callable[[Optional[Any]], Optional[Any]]:
def write_option(x: Any) -> Any:
if x is None:
return 'None'
else:
return ['Some', write_elt(x)]
return write_option


############################################################################
# Public classes
############################################################################|}
Expand Down Expand Up @@ -609,7 +632,8 @@ let rec json_writer env e =
sprintf "_atd_write_assoc_list_to_object(%s)"
(json_writer env value)
)
| Option (loc, e, an)
| Option (loc, e, an) ->
sprintf "_atd_write_option(%s)" (json_writer env e)
| Nullable (loc, e, an) ->
sprintf "_atd_write_nullable(%s)" (json_writer env e)
| Shared (loc, e, an) -> not_implemented loc "shared"
Expand Down Expand Up @@ -690,7 +714,8 @@ let rec json_reader env (e : type_expr) =
sprintf "_atd_read_assoc_object_into_list(%s)"
(json_reader env value)
)
| Option (loc, e, an)
| Option (loc, e, an) ->
sprintf "_atd_read_option(%s)" (json_reader env e)
| Nullable (loc, e, an) ->
sprintf "_atd_read_nullable(%s)" (json_reader env e)
| Shared (loc, e, an) -> not_implemented loc "shared"
Expand Down
27 changes: 25 additions & 2 deletions atdpy/test/python-expected/everything.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ def read_nullable(x: Any) -> Any:
return read_nullable


def _atd_read_option(read_elt: Callable[[Any], Any]) \
-> Callable[[Optional[Any]], Optional[Any]]:
def read_option(x: Any) -> Any:
if x == 'None':
return None
elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some':
return read_elt(x[1])
else:
_atd_bad_json('option', x)
raise AssertionError('impossible') # keep mypy happy
return read_option


def _atd_write_unit(x: Any) -> None:
if x is None:
return x
Expand Down Expand Up @@ -232,6 +245,16 @@ def write_nullable(x: Any) -> Any:
return write_nullable


def _atd_write_option(write_elt: Callable[[Any], Any]) \
-> Callable[[Optional[Any]], Optional[Any]]:
def write_option(x: Any) -> Any:
if x is None:
return 'None'
else:
return ['Some', write_elt(x)]
return write_option


############################################################################
# Public classes
############################################################################
Expand Down Expand Up @@ -499,7 +522,7 @@ def from_json(cls, x: Any) -> 'Root':
assoc3=_atd_read_assoc_array_into_dict(_atd_read_float, _atd_read_int)(x['assoc3']) if 'assoc3' in x else _atd_missing_json_field('Root', 'assoc3'),
assoc4=_atd_read_assoc_object_into_dict(_atd_read_int)(x['assoc4']) if 'assoc4' in x else _atd_missing_json_field('Root', 'assoc4'),
nullables=_atd_read_list(_atd_read_nullable(_atd_read_int))(x['nullables']) if 'nullables' in x else _atd_missing_json_field('Root', 'nullables'),
options=_atd_read_list(_atd_read_nullable(_atd_read_int))(x['options']) if 'options' in x else _atd_missing_json_field('Root', 'options'),
options=_atd_read_list(_atd_read_option(_atd_read_int))(x['options']) if 'options' in x else _atd_missing_json_field('Root', 'options'),
untyped_things=_atd_read_list((lambda x: x))(x['untyped_things']) if 'untyped_things' in x else _atd_missing_json_field('Root', 'untyped_things'),
parametrized_record=IntFloatParametrizedRecord.from_json(x['parametrized_record']) if 'parametrized_record' in x else _atd_missing_json_field('Root', 'parametrized_record'),
parametrized_tuple=KindParametrizedTuple.from_json(x['parametrized_tuple']) if 'parametrized_tuple' in x else _atd_missing_json_field('Root', 'parametrized_tuple'),
Expand All @@ -524,7 +547,7 @@ def to_json(self) -> Any:
res['assoc3'] = _atd_write_assoc_dict_to_array(_atd_write_float, _atd_write_int)(self.assoc3)
res['assoc4'] = _atd_write_assoc_dict_to_object(_atd_write_int)(self.assoc4)
res['nullables'] = _atd_write_list(_atd_write_nullable(_atd_write_int))(self.nullables)
res['options'] = _atd_write_list(_atd_write_nullable(_atd_write_int))(self.options)
res['options'] = _atd_write_list(_atd_write_option(_atd_write_int))(self.options)
res['untyped_things'] = _atd_write_list((lambda x: x))(self.untyped_things)
res['parametrized_record'] = (lambda x: x.to_json())(self.parametrized_record)
res['parametrized_tuple'] = (lambda x: x.to_json())(self.parametrized_tuple)
Expand Down
12 changes: 9 additions & 3 deletions atdpy/test/python-tests/test_atdpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,15 @@ def test_everything_to_json() -> None:
34
],
"options": [
56,
null,
78
[
"Some",
56
],
"None",
[
"Some",
78
]
],
"untyped_things": [
[
Expand Down
56 changes: 29 additions & 27 deletions doc/atdpy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,33 +234,35 @@ Reference
Type mapping
------------

+--------------------+----------------------+-------------------------+
| ATD type | Python type | JSON example |
+====================+======================+=========================+
| ``unit`` | ``None`` | ``null`` |
+--------------------+----------------------+-------------------------+
| ``bool`` | ``bool`` | ``True`` |
+--------------------+----------------------+-------------------------+
| ``int`` | ``int`` | ``42`` |
+--------------------+----------------------+-------------------------+
| ``float`` | ``float`` | ``6.28`` |
+--------------------+----------------------+-------------------------+
| ``string`` | ``str`` | ``"Hello"`` |
+--------------------+----------------------+-------------------------+
| ``int list`` | ``List[int]`` | ``[1, 2, 3]`` |
+--------------------+----------------------+-------------------------+
| ``(int * int)`` | ``Tuple[int, int]`` | ``[-1, 1]`` |
+--------------------+----------------------+-------------------------+
| ``int nullable`` | ``Union[int, None]`` | ``42`` or ``null`` |
+--------------------+----------------------+-------------------------+
| ``abstract`` | ``Any`` | anything |
+--------------------+----------------------+-------------------------+
| record type | class | ``{"id": 17}`` |
+--------------------+----------------------+-------------------------+
| ``[A | B of int]`` | ``Union[A, B]`` | ``"A"`` or ``["B", 5]`` |
+--------------------+----------------------+-------------------------+
| ``foo_bar`` | ``FooBar`` | |
+--------------------+----------------------+-------------------------+
+--------------------+----------------------+--------------------------------+
| ATD type | Python type | JSON example |
+====================+======================+================================+
| ``unit`` | ``None`` | ``null`` |
+--------------------+----------------------+--------------------------------+
| ``bool`` | ``bool`` | ``True`` |
+--------------------+----------------------+--------------------------------+
| ``int`` | ``int`` | ``42`` |
+--------------------+----------------------+--------------------------------+
| ``float`` | ``float`` | ``6.28`` |
+--------------------+----------------------+--------------------------------+
| ``string`` | ``str`` | ``"Hello"`` |
+--------------------+----------------------+--------------------------------+
| ``int list`` | ``List[int]`` | ``[1, 2, 3]`` |
+--------------------+----------------------+--------------------------------+
| ``(int * int)`` | ``Tuple[int, int]`` | ``[-1, 1]`` |
+--------------------+----------------------+--------------------------------+
| ``int nullable`` | ``Optional[int]`` | ``42`` or ``null`` |
+--------------------+----------------------+--------------------------------+
| ``int option`` | ``Optional[int]`` | ``["Some", 42]`` or ``"None"`` |
+--------------------+----------------------+--------------------------------+
| ``abstract`` | ``Any`` | anything |
+--------------------+----------------------+--------------------------------+
| record type | class | ``{"id": 17}`` |
+--------------------+----------------------+--------------------------------+
| ``[A | B of int]`` | ``Union[A, B]`` | ``"A"`` or ``["B", 5]`` |
+--------------------+----------------------+--------------------------------+
| ``foo_bar`` | ``FooBar`` | |
+--------------------+----------------------+--------------------------------+

Supported ATD annotations
-------------------------
Expand Down

0 comments on commit fc3bcfb

Please sign in to comment.