Skip to content

Commit b34cfbc

Browse files
authored
Merge pull request #22603 from github/rust/codegen-to-string
Rust: Add codegen for generating a toStringImpl that delegates to getAPrimaryQlClass
2 parents e258ded + ed68b0e commit b34cfbc

196 files changed

Lines changed: 729 additions & 439 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎misc/codegen/generators/qlgen.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ def _humanize(s: str) -> str:
8383

8484

8585
_format_re = re.compile(r"\{(\w+)\}")
86+
# Regular expression to find manual `toStringImpl`s
87+
_to_string_impl_re = re.compile(r"\bstring\s+toStringImpl\s*\(")
8688

8789

8890
def _get_doc(cls: schema.Class, prop: schema.Property, plural=None):
@@ -117,6 +119,7 @@ def _get_doc(cls: schema.Class, prop: schema.Property, plural=None):
117119
@dataclasses.dataclass
118120
class Resolver:
119121
lookup: typing.Dict[str, schema.ClassBase]
122+
custom_to_string_impls: typing.Set[str] = dataclasses.field(default_factory=set)
120123
_property_cache: typing.Dict[tuple[int, int], ql.Property] = dataclasses.field(
121124
default_factory=dict, init=False
122125
)
@@ -237,6 +240,10 @@ def get_ql_class(self, cls: schema.Class) -> ql.Class:
237240
hideable="ql_hideable" in cls.pragmas,
238241
internal="ql_internal" in cls.pragmas,
239242
cfg=cls.cfg,
243+
to_string_impl_from_primary_class=bool(
244+
cls.pragmas.get("ql_to_string_impl_from_primary_class")
245+
)
246+
and cls.name not in self.custom_to_string_impls,
240247
)
241248
return self._class_cache[cache_key]
242249

@@ -496,7 +503,15 @@ def generate(opts, renderer):
496503

497504
data = schemaloader.load_file(input)
498505

499-
resolver = Resolver(data.classes)
506+
# Schema class names whose wrapper define a manual `toStringImpl`
507+
custom_to_string_impls = {
508+
cls.name
509+
for cls in data.classes.values()
510+
if not cls.imported
511+
and (stub_out / _get_path_impl(cls)).is_file()
512+
and _to_string_impl_re.search((stub_out / _get_path_impl(cls)).read_text())
513+
}
514+
resolver = Resolver(data.classes, custom_to_string_impls)
500515

501516
classes = {
502517
name: resolver.get_ql_class(cls)
@@ -522,7 +537,6 @@ def generate(opts, renderer):
522537
with renderer.manage(
523538
generated=generated, stubs=stubs, registry=registry, force=opts.force
524539
) as renderer:
525-
526540
db_classes = [
527541
cls for name, cls in classes.items() if not data.classes[name].synth
528542
]

‎misc/codegen/lib/ql.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class Class:
117117
doc: List[str] = field(default_factory=list)
118118
hideable: bool = False
119119
cfg: bool = False
120+
to_string_impl_from_primary_class: bool = False
120121

121122
def __post_init__(self):
122123
def get_bases(bases):

‎misc/codegen/lib/schemadefs.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ def __getitem__(self, item):
263263

264264
ql.add(_Parametrized(_ClassPragma("default_doc_name"), factory=lambda doc: doc))
265265
ql.add(_ClassPragma("hideable", inherited=True))
266+
ql.add(
267+
_Parametrized(
268+
_ClassPragma("to_string_impl_from_primary_class", inherited=True),
269+
factory=lambda enabled: enabled,
270+
)
271+
)
266272
ql.add(_Pragma("internal"))
267273
ql.add(_Parametrized(_Pragma("name"), factory=lambda name: name))
268274
ql.add(_Parametrized(_PropertyPragma("db_table_name"), factory=lambda name: name))

‎misc/codegen/templates/ql_class.mustache‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ module Generated {
7777
{{/root}}
7878
{{#final}}
7979
override string getAPrimaryQlClass() { result = "{{name}}" }
80+
{{#to_string_impl_from_primary_class}}
81+
82+
override string toStringImpl() { result = this.getAPrimaryQlClass() }
83+
{{/to_string_impl_from_primary_class}}
8084
{{/final}}
8185
{{#properties}}
8286

‎misc/codegen/test/test_qlgen.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,59 @@ def test_one_empty_class(generate_classes):
196196
}
197197

198198

199+
def test_to_string_impl_from_primary_class(generate_classes):
200+
assert generate_classes(
201+
[
202+
schema.Class(
203+
"A",
204+
derived={"B"},
205+
pragmas={"ql_to_string_impl_from_primary_class": True},
206+
),
207+
schema.Class(
208+
"B",
209+
bases=["A"],
210+
pragmas={"ql_to_string_impl_from_primary_class": True},
211+
),
212+
]
213+
) == {
214+
"A.qll": (
215+
a_ql_class_public(name="A"),
216+
a_ql_stub(name="A"),
217+
a_ql_class(
218+
name="A",
219+
imports=[stub_import_prefix + "A"],
220+
to_string_impl_from_primary_class=True,
221+
),
222+
),
223+
"B.qll": (
224+
a_ql_class_public(name="B", imports=[stub_import_prefix + "A"]),
225+
a_ql_stub(name="B"),
226+
a_ql_class(
227+
name="B",
228+
final=True,
229+
bases=["A"],
230+
bases_impl=["AImpl::A"],
231+
imports=[
232+
stub_import_prefix_internal + "AImpl::Impl as AImpl",
233+
],
234+
to_string_impl_from_primary_class=True,
235+
),
236+
),
237+
}
238+
239+
240+
def test_to_string_impl_from_primary_class_is_not_generated_when_custom():
241+
cls = schema.Class(
242+
"A",
243+
pragmas={"ql_to_string_impl_from_primary_class": True},
244+
)
245+
# Pass `A` as a class with a custom `toStringImpl`
246+
resolver = qlgen.Resolver({"A": cls}, {"A"})
247+
248+
# Resolving `A` gives a class where `to_string_impl_from_primary_class` does not hold.
249+
assert not resolver.get_ql_class(cls).to_string_impl_from_primary_class
250+
251+
199252
def test_one_empty_internal_class(generate_classes):
200253
assert generate_classes([schema.Class("A", pragmas=["ql_internal"])]) == {
201254
"A.qll": (

‎misc/codegen/test/test_schemaloader.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,29 @@ class A:
382382
}
383383

384384

385+
def test_inherited_to_string_impl_from_primary_class_pragma():
386+
@load
387+
class data:
388+
@defs.ql.to_string_impl_from_primary_class(True)
389+
class A:
390+
pass
391+
392+
class B(A):
393+
pass
394+
395+
@defs.ql.to_string_impl_from_primary_class(False)
396+
class C(B):
397+
pass
398+
399+
class D(C):
400+
pass
401+
402+
assert data.classes["A"].pragmas["ql_to_string_impl_from_primary_class"] is True
403+
assert data.classes["B"].pragmas["ql_to_string_impl_from_primary_class"] is True
404+
assert data.classes["C"].pragmas["ql_to_string_impl_from_primary_class"] is False
405+
assert data.classes["D"].pragmas["ql_to_string_impl_from_primary_class"] is False
406+
407+
385408
def test_synth_from_class():
386409
@load
387410
class data:

0 commit comments

Comments
 (0)