Skip to content

Commit 2359e4b

Browse files
FireChickenProductivitypre-commit-ci[bot]nriley
authored
Support dictating Python generic types (#2107)
examples of using the <user.python_generic_type> capture (spoken form -> result): `list of string` -> `list[str]` `list of string or integer` -> `list[str | int]` types can be nested with `of`: `list of optional of integer` -> `list[Optional[int]]` `and` can be used for multiple arguments: `tuple of integer and float` -> `tuple[int, float]` `done` can be used to exit a nesting: `tuple of optional of integer done string` -> `tuple[Optional[int], str]` user defined type names are capitalized `list of custom type` -> `list[CustomType]` saying `type` first refers to a custom type `type list of type integer` -> `List[Integer]` I started trying to abstract out the general pattern for implementing generic typing and consent currently refactored some of the Java generic types code. Typing is different in Python than Java, so the default grammar extends the `type`, `is type`, and `returns` rather than being able to just say the name of a generic type to have it typed out. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Nicholas Riley <[email protected]>
1 parent a6048e2 commit 2359e4b

File tree

4 files changed

+250
-80
lines changed

4 files changed

+250
-80
lines changed

lang/java/java.py

Lines changed: 18 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from talon import Context, Module, actions, settings
66

77
from ...core.described_functions import create_described_insert_between
8+
from ..tags.generic_types import format_type_parameter_arguments
89
from ..tags.operators import Operators
910

1011
ctx = Context()
@@ -144,106 +145,43 @@ def public_camel_case_format_variable(variable: str):
144145
# we plan on abstracting out from the specific implementations into a general grammar
145146

146147

147-
@mod.capture(rule="{user.java_boxed_type} | <user.text>")
148-
def java_type_parameter_argument(m) -> str:
148+
@ctx.capture(
149+
"user.generic_type_parameter_argument", rule="{user.java_boxed_type} | <user.text>"
150+
)
151+
def generic_type_parameter_argument(m) -> str:
149152
"""A Java type parameter for a generic data structure"""
150153
with suppress(AttributeError):
151154
return m.java_boxed_type
152155
return public_camel_case_format_variable(m.text)
153156

154157

155-
@mod.capture(rule="[type] {user.java_generic_data_structure} | type <user.text>")
156-
def java_generic_data_structure(m) -> str:
158+
@ctx.capture(
159+
"user.generic_data_structure",
160+
rule="[type] {user.java_generic_data_structure} | type <user.text>",
161+
)
162+
def generic_data_structure(m) -> str:
157163
"""A Java generic data structure that takes type parameter arguments"""
158164
with suppress(AttributeError):
159165
return m.java_generic_data_structure
160166
return public_camel_case_format_variable(m.text)
161167

162168

163-
class GenericTypeConnector(Enum):
164-
AND = auto()
165-
OF = auto()
166-
DONE = auto()
167-
168-
169-
@mod.capture(rule="done")
170-
def java_generic_type_connector_done(m) -> GenericTypeConnector:
171-
"""Denotes ending a nested generic type"""
172-
return GenericTypeConnector.DONE
173-
174-
175-
@mod.capture(rule="and|of|<user.java_generic_type_connector_done>")
176-
def java_generic_type_connector(m) -> GenericTypeConnector:
177-
"""Determines how to put generic type parameters together"""
178-
with suppress(AttributeError):
179-
return m.java_generic_type_connector_done
180-
return GenericTypeConnector[m[0].upper()]
181-
182-
183-
@mod.capture(
184-
rule="<user.java_generic_type_connector> <user.java_type_parameter_argument> [<user.java_generic_type_connector_done>]+"
185-
)
186-
def java_generic_type_continuation(m) -> list[Union[GenericTypeConnector, str]]:
187-
"""A generic type parameter that goes after the first using connectors"""
188-
result = [m.java_generic_type_connector, m.java_type_parameter_argument]
189-
with suppress(AttributeError):
190-
dones = m.java_generic_type_connector_done_list
191-
result.extend(dones)
192-
return result
193-
194-
195-
@mod.capture(rule="<user.java_generic_type_continuation>+")
196-
def java_generic_type_additional_type_parameters(
197-
m,
198-
) -> list[Union[GenericTypeConnector, str]]:
199-
"""Type parameters for a generic data structure after the first one"""
200-
result = []
201-
for continuation in m.java_generic_type_continuation_list:
202-
result.extend(continuation)
203-
return result
204-
205-
206-
def is_immediately_after_nesting_exit(pieces: list[str]) -> bool:
207-
return len(pieces) >= 1 and pieces[-1] == ">"
208-
209-
210-
@mod.capture(
211-
rule="<user.java_type_parameter_argument> [<user.java_generic_type_additional_type_parameters>]"
169+
@ctx.capture(
170+
"user.generic_type_parameter_arguments",
171+
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]",
212172
)
213-
def java_type_parameter_arguments(m) -> str:
173+
def generic_type_parameter_arguments(m) -> str:
214174
"""Formatted Java type parameter arguments"""
215-
parameters = [m.java_type_parameter_argument]
216-
with suppress(AttributeError):
217-
parameters.extend(m.java_generic_type_additional_type_parameters)
218-
pieces = []
219-
nesting: int = 0
220-
for parameter in parameters:
221-
if isinstance(parameter, str):
222-
if is_immediately_after_nesting_exit(pieces):
223-
pieces.append(", ")
224-
pieces.append(parameter)
225-
else:
226-
match parameter:
227-
case GenericTypeConnector.AND:
228-
pieces.append(", ")
229-
case GenericTypeConnector.OF:
230-
pieces.append("<")
231-
nesting += 1
232-
case GenericTypeConnector.DONE:
233-
pieces.append(">")
234-
nesting -= 1
235-
if nesting > 0:
236-
pieces.append(">" * nesting)
237-
return "".join(pieces)
175+
return format_type_parameter_arguments(m, ", ", "<", ">")
238176

239177

240178
@mod.capture(
241-
rule="<user.java_generic_data_structure> of <user.java_type_parameter_arguments>"
179+
rule="<user.generic_data_structure> of <user.generic_type_parameter_arguments>"
242180
)
243181
def java_generic_type(m) -> str:
244182
"""A generic type with specific type parameters"""
245-
parameters = m.java_type_parameter_arguments
246-
return f"{m.java_generic_data_structure}<{parameters}>"
183+
parameters = m.generic_type_parameter_arguments
184+
return f"{m.generic_data_structure}<{parameters}>"
247185

248186

249187
# End of unstable section

lang/python/python.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import re
2+
from contextlib import suppress
23

34
from talon import Context, Module, actions, settings
45

56
from ...core.described_functions import create_described_insert_between
7+
from ..tags.generic_types import (
8+
SimpleLanguageSpecificTypeConnector,
9+
format_type_parameter_arguments,
10+
)
611
from ..tags.operators import Operators
712

813
mod = Module()
@@ -103,6 +108,77 @@
103108
for exception in exception_list
104109
}
105110

111+
# This is not part of the long term stable API
112+
# After we implement generics support for several languages,
113+
# we plan on abstracting out from the specific implementations into a general grammar
114+
115+
mod.list(
116+
"python_generic_type", desc="A python type that takes type parameter arguments"
117+
)
118+
119+
# this should be moved to a talon-list file after this becomes stable
120+
ctx.lists["user.python_generic_type"] = {
121+
"callable": "Callable",
122+
"dictionary": "dict",
123+
"iterable": "Iterable",
124+
"list": "list",
125+
"optional": "Optional",
126+
"set": "set",
127+
"tuple": "tuple",
128+
"union": "Union",
129+
}
130+
131+
132+
@ctx.capture(
133+
"user.generic_type_parameter_argument", rule="<user.code_type> | [type] <user.text>"
134+
)
135+
def generic_type_parameter_argument(m) -> str:
136+
"""A Python type parameter for a generic data structure"""
137+
with suppress(AttributeError):
138+
return m.code_type
139+
return actions.user.formatted_text(m.text, "PUBLIC_CAMEL_CASE")
140+
141+
142+
@ctx.capture(
143+
"user.generic_data_structure",
144+
rule="{user.python_generic_type} | [type] <user.text>",
145+
)
146+
def generic_data_structure(m) -> str:
147+
"""A Python generic data structure that takes type parameter arguments"""
148+
with suppress(AttributeError):
149+
return m.python_generic_type
150+
return actions.user.formatted_text(m.text, "PUBLIC_CAMEL_CASE")
151+
152+
153+
@ctx.capture(
154+
"user.generic_type_connector", rule="<user.common_generic_type_connector>|or"
155+
)
156+
def generic_type_connector(m) -> SimpleLanguageSpecificTypeConnector:
157+
"""A Python specific type connector for union types"""
158+
with suppress(AttributeError):
159+
return m.common_generic_type_connector
160+
return SimpleLanguageSpecificTypeConnector(" | ")
161+
162+
163+
@ctx.capture(
164+
"user.generic_type_parameter_arguments",
165+
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]",
166+
)
167+
def generic_type_parameter_arguments(m) -> str:
168+
return format_type_parameter_arguments(m, ", ", "[", "]")
169+
170+
171+
@mod.capture(
172+
rule="<user.generic_data_structure> of <user.generic_type_parameter_arguments>"
173+
)
174+
def python_generic_type(m) -> str:
175+
"""A generic type with specific type parameters"""
176+
parameters = m.generic_type_parameter_arguments
177+
return f"{m.generic_data_structure}[{parameters}]"
178+
179+
180+
# End of unstable section
181+
106182
operators = Operators(
107183
# code_operators_array
108184
SUBSCRIPT=create_described_insert_between("[", "]"),

lang/python/python.talon

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,23 @@ import <user.code_libraries>:
5151
key(end enter)
5252

5353
from import: user.insert_snippet_by_name("importFromStatement")
54+
55+
# the generic type commands are currently unstable and may be subject to change
56+
57+
# examples of using the <user.python_generic_type> capture:
58+
# "list of string" -> list[str]
59+
# "list of string or integer" -> list[str | int]
60+
# types can be nested with `of`:
61+
# "list of optional of integer" -> list[Optional[int]]
62+
# `and` can be used for multiple arguments:
63+
# "tuple of integer and float" -> tuple[int, float]
64+
# `done` can be used to exit a nesting:
65+
# "tuple of optional of integer done string" -> tuple[Optional[int], str]
66+
# user defined type names are capitalized
67+
# "list of custom type" -> list[CustomType]
68+
# saying `type` first refers to a custom type
69+
# "type list of type integer" -> List[Integer]
70+
71+
type <user.python_generic_type>: insert(python_generic_type)
72+
returns <user.python_generic_type>: insert(" -> {python_generic_type}")
73+
is type <user.python_generic_type>: insert(": {python_generic_type}")

lang/tags/generic_types.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# This functionality is unstable and subject to change
2+
# we want to implement generic type support for several languages before finalizing the general abstraction
3+
4+
from contextlib import suppress
5+
from dataclasses import dataclass
6+
from enum import Enum, auto
7+
from typing import Union
8+
9+
from talon import Module
10+
11+
12+
class CommonTypeConnector(Enum):
13+
"""A common type connector for connecting type arguments for a generic type"""
14+
15+
AND = auto()
16+
OF = auto()
17+
DONE = auto()
18+
19+
20+
@dataclass(slots=True)
21+
class SimpleLanguageSpecificTypeConnector:
22+
"""A type connector that only requires inserting text with no other complexity,
23+
e.g. Python's `|` for union types
24+
"""
25+
26+
text: str
27+
28+
29+
TypeConnector = Union[CommonTypeConnector, SimpleLanguageSpecificTypeConnector]
30+
31+
mod = Module()
32+
33+
# implement the following for a specific language
34+
35+
36+
@mod.capture
37+
def generic_type_parameter_argument(m) -> str:
38+
"""A type parameter for a generic data structure. This should include standard types of a language and appropriate formatting of arbitrary text for user types"""
39+
pass
40+
41+
42+
@mod.capture
43+
def generic_data_structure(m) -> str:
44+
"""A generic data structure that takes type parameter arguments"""
45+
pass
46+
47+
48+
@mod.capture(
49+
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]"
50+
)
51+
def generic_type_parameter_arguments(m) -> str:
52+
"""This combines type parameter arguments, connectors, and the containing type into a formatted string. This is usually formatted using format_type_parameter_arguments"""
53+
pass
54+
55+
56+
# end of language specific section
57+
58+
59+
@mod.capture(rule="done")
60+
def generic_type_connector_done(m) -> CommonTypeConnector:
61+
"""Denotes ending a nested generic type"""
62+
return CommonTypeConnector.DONE
63+
64+
65+
@mod.capture(rule="and|of|<user.generic_type_connector_done>")
66+
def common_generic_type_connector(m) -> CommonTypeConnector:
67+
"""A common type connector for generic types"""
68+
with suppress(AttributeError):
69+
return m.generic_type_connector_done
70+
return CommonTypeConnector[m[0].upper()]
71+
72+
73+
@mod.capture(rule="<user.common_generic_type_connector>")
74+
def generic_type_connector(m) -> TypeConnector:
75+
"""A generic type connector for determining how to put type parameters together.
76+
Override on a per language basis to add additional connectors.
77+
"""
78+
return m.common_generic_type_connector
79+
80+
81+
@mod.capture(
82+
rule="<user.generic_type_connector> <user.generic_type_parameter_argument> [<user.generic_type_connector_done>]+"
83+
)
84+
def generic_type_continuation(m) -> list[Union[TypeConnector, str]]:
85+
"""A generic type parameter that goes after the first using connectors"""
86+
result = [m.generic_type_connector, m.generic_type_parameter_argument]
87+
with suppress(AttributeError):
88+
dones = m.generic_type_connector_done_list
89+
result.extend(dones)
90+
return result
91+
92+
93+
@mod.capture(rule="<user.generic_type_continuation>+")
94+
def generic_type_additional_type_parameters(
95+
m,
96+
) -> list[Union[TypeConnector, str]]:
97+
"""Type parameters for a generic data structure after the first one"""
98+
result = []
99+
for continuation in m.generic_type_continuation_list:
100+
result.extend(continuation)
101+
return result
102+
103+
104+
def format_type_parameter_arguments(
105+
m,
106+
argument_separator: str,
107+
generic_parameters_start: str,
108+
generic_parameters_end: str,
109+
) -> str:
110+
"""Formats type parameter arguments for languages with simple generic typing"""
111+
parameters = [m.generic_type_parameter_argument]
112+
with suppress(AttributeError):
113+
parameters.extend(m.generic_type_additional_type_parameters)
114+
pieces = []
115+
nesting: int = 0
116+
is_immediately_after_nesting_exit = False
117+
for parameter in parameters:
118+
match parameter:
119+
case CommonTypeConnector.AND:
120+
pieces.append(argument_separator)
121+
case CommonTypeConnector.OF:
122+
pieces.append(generic_parameters_start)
123+
nesting += 1
124+
case CommonTypeConnector.DONE:
125+
pieces.append(generic_parameters_end)
126+
nesting -= 1
127+
case SimpleLanguageSpecificTypeConnector():
128+
pieces.append(parameter.text)
129+
case str():
130+
if is_immediately_after_nesting_exit:
131+
pieces.append(argument_separator)
132+
pieces.append(parameter)
133+
is_immediately_after_nesting_exit = parameter == CommonTypeConnector.DONE
134+
if nesting > 0:
135+
pieces.append(generic_parameters_end * nesting)
136+
return "".join(pieces)

0 commit comments

Comments
 (0)