From b0ae96fb561eb5d63b33589f914fa658dd722914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 23 May 2025 16:25:29 +0200 Subject: [PATCH 1/3] Allow signed numbers in literals --- src/docstub/doctype.lark | 4 ++-- tests/test_docstrings.py | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/docstub/doctype.lark b/src/docstub/doctype.lark index 1243a73..e6028ed 100644 --- a/src/docstub/doctype.lark +++ b/src/docstub/doctype.lark @@ -58,7 +58,7 @@ natlang_literal: "{" literal_item ("," literal_item)* "}" // An single item in a literal expression (or `optional`). We must also allow // for qualified names, since a "class" or enum can be used as a literal too. -?literal_item: ELLIPSES | STRING | NUMBER | qualname +?literal_item: ELLIPSES | STRING | SIGNED_NUMBER | qualname // Natural language forms of the subscription expression for containers. @@ -147,6 +147,6 @@ NAME: /[^\W\d][\w-]*/ %import python (STRING) -%import common (NUMBER, WS_INLINE) +%import common (SIGNED_NUMBER, NUMBER, WS_INLINE) %ignore WS_INLINE diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index e03f6f0..af78fcb 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -108,12 +108,26 @@ def test_subscription_error(self, doctype): ("doctype", "expected"), [ ("{0}", "Literal[0]"), - ("{'a', 1, None, False}", "Literal['a', 1, None, False]"), - ("dict[{'a', 'b'}, int]", "dict[Literal['a', 'b'], int]"), + ("{-1, 1}", "Literal[-1, 1]"), + ("{None}", "Literal[None]"), + ("{True, False}", "Literal[True, False]"), + ("""{'a', "bar"}""", """Literal['a', "bar"]"""), + # Enum ("{SomeEnum.FIRST}", "Literal[SomeEnum_FIRST]"), ("{`SomeEnum.FIRST`, 1}", "Literal[SomeEnum_FIRST, 1]"), ("{:ref:`SomeEnum.FIRST`, 2}", "Literal[SomeEnum_FIRST, 2]"), ("{:py:ref:`SomeEnum.FIRST`, 3}", "Literal[SomeEnum_FIRST, 3]"), + # Nesting + ("dict[{'a', 'b'}, int]", "dict[Literal['a', 'b'], int]"), + # These aren't officially valid as an argument to `Literal` (yet) + # https://typing.python.org/en/latest/spec/literal.html + # TODO figure out how docstub should deal with these + ("{-2., 1.}", "Literal[-2., 1.]"), + pytest.param( + "{-inf, inf, nan}", + "Literal[, 1.]", + marks=pytest.mark.xfail(reason="unsure how to support"), + ), ], ) def test_literals(self, doctype, expected): From e9625036faeaa774ddc86d75c93fcc2ad8b3b21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 23 May 2025 16:26:17 +0200 Subject: [PATCH 2/3] Disallow floats & negative ints in array shapes --- src/docstub/doctype.lark | 6 +++--- tests/test_docstrings.py | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/docstub/doctype.lark b/src/docstub/doctype.lark index e6028ed..7093563 100644 --- a/src/docstub/doctype.lark +++ b/src/docstub/doctype.lark @@ -114,7 +114,7 @@ ARRAY_NAME: "array" | "ndarray" | "array-like" | "array_like" // NumPy arrays, this information is dropped during the transformation. shape: "(" dim ",)" | "(" leading_optional_dim? dim (("," dim | insert_optional_dim))* ")" - | NUMBER "-"? "D" + | INT "-"? "D" // Optional dimensions in a `shape` expression placed at the start, @@ -129,7 +129,7 @@ shape: "(" dim ",)" // Dimension can be a number, ellipses ('...') or a simple name. A simple name // can be bound to a specific number, e.g. `N=3`. -?dim: NUMBER | ELLIPSES | NAME ("=" NUMBER)? +?dim: INT | ELLIPSES | NAME ("=" INT)? // Optional information about a parameter has a default value, added after the @@ -147,6 +147,6 @@ NAME: /[^\W\d][\w-]*/ %import python (STRING) -%import common (SIGNED_NUMBER, NUMBER, WS_INLINE) +%import common (SIGNED_NUMBER, INT, WS_INLINE) %ignore WS_INLINE diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index af78fcb..fd4f619 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -202,6 +202,13 @@ def escape(name: str) -> str: assert annotation.value == expected # fmt: on + @pytest.mark.parametrize("shape", ["(-1, 3)", "(1.0, 2)", "-3D", "-2-D"]) + def test_natlang_array_invalid_shape(self, shape): + doctype = f"array of shape {shape}" + transformer = DoctypeTransformer() + with pytest.raises(lark.exceptions.UnexpectedInput): + transformer.doctype_to_annotation(doctype) + def test_unknown_name(self): # Simple unknown name is aliased to typing.Any transformer = DoctypeTransformer() From 2afcb5f048e39457d61fd16af9f7615d5d9d18a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 23 May 2025 18:48:25 +0200 Subject: [PATCH 3/3] Fix wording --- src/docstub/doctype.lark | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docstub/doctype.lark b/src/docstub/doctype.lark index 7093563..d62d389 100644 --- a/src/docstub/doctype.lark +++ b/src/docstub/doctype.lark @@ -62,8 +62,8 @@ natlang_literal: "{" literal_item ("," literal_item)* "}" // Natural language forms of the subscription expression for containers. -// These forms allow nesting allow nesting in and with other expressions. But -// it's discouraged to do so extensively to maintain readability. +// These forms allow nesting with other expressions. But it's discouraged to do +// so extensively to maintain readability. natlang_container: qualname "of" qualname _PLURAL_S? | qualname "of" "(" union ")" | _natlang_tuple