Skip to content

Commit 2557ddb

Browse files
committed
WIP
1 parent 971c7eb commit 2557ddb

3 files changed

Lines changed: 81 additions & 11 deletions

File tree

src/docstub/_doctype.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from dataclasses import dataclass
88
from pathlib import Path
99
from textwrap import indent
10-
from typing import Final
10+
from typing import Final, Self
1111

1212
import lark
1313
import lark.visitors
@@ -124,7 +124,7 @@ class Expr:
124124
"""
125125

126126
rule: str
127-
children: list[Expr | Term]
127+
children: list[Self | Term]
128128

129129
@property
130130
def terms(self):
@@ -226,6 +226,29 @@ def qualname(self, tree):
226226
)
227227
return _qualname
228228

229+
def qualname(self, tree):
230+
"""
231+
Parameters
232+
----------
233+
tree : lark.Tree
234+
235+
Returns
236+
-------
237+
out : Term
238+
"""
239+
children = tree.children
240+
_qualname = ".".join(children)
241+
242+
if _qualname in BLACKLISTED_QUALNAMES:
243+
raise BlacklistedQualname(_qualname)
244+
245+
_qualname = Term(
246+
_qualname,
247+
kind=TermKind.NAME,
248+
pos=(tree.meta.start_pos, tree.meta.end_pos),
249+
)
250+
return _qualname
251+
229252
def ELLIPSES(self, token):
230253
"""
231254
Parameters
@@ -262,7 +285,7 @@ def subscription(self, tree):
262285
-------
263286
out : Expr
264287
"""
265-
return self._format_subscription(tree.children)
288+
return self._format_subscription(tree.children, rule="subscription")
266289

267290
def param_spec(self, tree):
268291
"""
@@ -295,6 +318,23 @@ def callable(self, tree):
295318
"""
296319
return self._format_subscription(tree.children, rule="callable")
297320

321+
def literal(self, tree):
322+
"""
323+
Parameters
324+
----------
325+
tree : lark.Tree
326+
327+
Returns
328+
-------
329+
out : Expr
330+
"""
331+
items = [
332+
Term("Literal", kind=TermKind.NAME),
333+
*tree.children,
334+
]
335+
out = self._format_subscription(items, rule="literal")
336+
return out
337+
298338
def natlang_literal(self, tree):
299339
"""
300340
Parameters
@@ -306,7 +346,7 @@ def natlang_literal(self, tree):
306346
out : Expr
307347
"""
308348
items = [
309-
Term("Literal", kind=TermKind.SYNTAX),
349+
Term("Literal", kind=TermKind.NAME),
310350
*tree.children,
311351
]
312352
out = self._format_subscription(items, rule="natlang_literal")
@@ -335,7 +375,8 @@ def literal_item(self, tree):
335375
kind = TermKind.LITERAL
336376
if isinstance(item, Term):
337377
kind = item.kind
338-
return Term(item, kind=kind, pos=(tree.meta.start_pos, tree.meta.end_pos))
378+
out = Term(item, kind=kind, pos=(tree.meta.start_pos, tree.meta.end_pos))
379+
return out
339380

340381
def natlang_container(self, tree):
341382
"""
@@ -427,7 +468,7 @@ def extra_info(self, tree):
427468
logger.debug("dropping extra info")
428469
return lark.Discard
429470

430-
def _format_subscription(self, sequence, rule="subscription"):
471+
def _format_subscription(self, sequence, *, rule):
431472
"""
432473
Parameters
433474
----------

src/docstub/doctype.lark

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ _annotation_with_meta: _type ("," optional_info)?
1919
// nested construct of types.
2020
_type: qualname
2121
| union
22+
| literal
2223
| subscription
2324
| callable
2425
| natlang_literal
@@ -68,6 +69,13 @@ ELLIPSES: "..."
6869
natlang_literal: "{" literal_item ("," literal_item)* "}"
6970

7071

72+
// A literal expression as supported by Python proper. The rule "subscription"
73+
// isn't allowed to contain "literal_items", so we need to define this.
74+
// Assign a higher priority so that things like `Literal[Some.ENUM]` is marked
75+
// as a literal expression.
76+
literal.1: "Literal[" literal_item ("," literal_item)* "]"
77+
78+
7179
// An single item in a literal expression (or `optional`). We must also allow
7280
// for qualified names, since a "class" or enum can be used as a literal too.
7381
literal_item: ELLIPSES | STRING | SIGNED_NUMBER | qualname

tests/test_doctype.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ def test_subscription_error(self, doctype):
115115
with pytest.raises(lark.exceptions.UnexpectedInput):
116116
parse_doctype(doctype)
117117

118+
@pytest.mark.parametrize(
119+
("doctype"),
120+
[
121+
"Literal[0]",
122+
"Literal[-1, 1]",
123+
"Literal[None]",
124+
"Literal[True, False]",
125+
"""Literal['a', "bar"]""",
126+
# Enum
127+
"Literal[SomeEnum.FIRST]",
128+
"Literal[SomeEnum.FIRST, 1]",
129+
"Literal[SomeEnum.FIRST, 2]",
130+
"Literal[SomeEnum.FIRST, 3]",
131+
# Nesting
132+
"dict[Literal['a', 'b'], int]",
133+
],
134+
)
135+
def test_literals(self, doctype):
136+
expr = parse_doctype(doctype)
137+
assert expr.as_code() == doctype
138+
118139
@pytest.mark.parametrize(
119140
("doctype", "expected"),
120141
[
@@ -124,10 +145,10 @@ def test_subscription_error(self, doctype):
124145
("{True, False}", "Literal[True, False]"),
125146
("""{'a', "bar"}""", """Literal['a', "bar"]"""),
126147
# Enum
127-
("{SomeEnum.FIRST}", "Literal[SomeEnum_FIRST]"),
128-
("{`SomeEnum.FIRST`, 1}", "Literal[SomeEnum_FIRST, 1]"),
129-
("{:ref:`SomeEnum.FIRST`, 2}", "Literal[SomeEnum_FIRST, 2]"),
130-
("{:py:ref:`SomeEnum.FIRST`, 3}", "Literal[SomeEnum_FIRST, 3]"),
148+
("{SomeEnum.FIRST}", "Literal[SomeEnum.FIRST]"),
149+
("{`SomeEnum.FIRST`, 1}", "Literal[SomeEnum.FIRST, 1]"),
150+
("{:ref:`SomeEnum.FIRST`, 2}", "Literal[SomeEnum.FIRST, 2]"),
151+
("{:py:ref:`SomeEnum.FIRST`, 3}", "Literal[SomeEnum.FIRST, 3]"),
131152
# Nesting
132153
("dict[{'a', 'b'}, int]", "dict[Literal['a', 'b'], int]"),
133154
# These aren't officially valid as an argument to `Literal` (yet)
@@ -141,7 +162,7 @@ def test_subscription_error(self, doctype):
141162
),
142163
],
143164
)
144-
def test_literals(self, doctype, expected):
165+
def test_natlang_literals(self, doctype, expected):
145166
expr = parse_doctype(doctype)
146167
assert expr.as_code() == expected
147168

0 commit comments

Comments
 (0)