Skip to content

Commit 1dcd605

Browse files
committed
Codegen: improve implementation of generated parent/child relationship
This improves the implementation of the generated parent/child relationship by adding a new `all_children` field to `ql.Class` which lists all children (both direct and inherited) of a class, carefully avoiding duplicating children in case of diamond inheritance. This: * simplifies the generated code, * avoid children ambiguities in case of diamond inheritance. This only comes with some changes in the order of children in the generated tests (we were previously sorting bases alphabetically there). For the rest this should be a non-functional change.
1 parent c4a385f commit 1dcd605

File tree

19 files changed

+1941
-5059
lines changed

19 files changed

+1941
-5059
lines changed

misc/codegen/generators/qlgen.py

Lines changed: 196 additions & 171 deletions
Large diffs are not rendered by default.

misc/codegen/generators/rusttestgen.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,12 @@ def generate(opts, renderer):
5959
registry=opts.ql_test_output / ".generated_tests.list",
6060
force=opts.force,
6161
) as renderer:
62+
63+
resolver = qlgen.Resolver(schema.classes)
6264
for cls in schema.classes.values():
6365
if cls.imported:
6466
continue
65-
if (
66-
qlgen.should_skip_qltest(cls, schema.classes)
67-
or "rust_skip_doc_test" in cls.pragmas
68-
):
67+
if resolver.should_skip_qltest(cls) or "rust_skip_doc_test" in cls.pragmas:
6968
continue
7069
code = _get_code(cls.doc)
7170
for p in schema.iter_properties(cls.name):

misc/codegen/lib/ql.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class Property:
3737
is_optional: bool = False
3838
is_predicate: bool = False
3939
is_unordered: bool = False
40-
prev_child: Optional[str] = None
4140
qltest_skip: bool = False
4241
description: List[str] = field(default_factory=list)
4342
doc: Optional[str] = None
@@ -48,6 +47,7 @@ class Property:
4847
type_is_self: bool = False
4948
internal: bool = False
5049
cfg: bool = False
50+
is_child: bool = False
5151

5252
def __post_init__(self):
5353
if self.tableparams:
@@ -76,10 +76,6 @@ def is_repeated(self):
7676
def is_single(self):
7777
return not (self.is_optional or self.is_repeated or self.is_predicate)
7878

79-
@property
80-
def is_child(self):
81-
return self.prev_child is not None
82-
8379
@property
8480
def is_indexed(self) -> bool:
8581
return self.is_repeated and not self.is_unordered
@@ -89,6 +85,12 @@ def type_alias(self) -> Optional[str]:
8985
return self.type + "Alias" if self.type_is_self else self.type
9086

9187

88+
@dataclass
89+
class Child:
90+
property: Property
91+
prev: str = ""
92+
93+
9294
@dataclass
9395
class Base:
9496
base: str
@@ -107,6 +109,7 @@ class Class:
107109
bases_impl: List[Base] = field(default_factory=list)
108110
final: bool = False
109111
properties: List[Property] = field(default_factory=list)
112+
all_children: List[Child] = field(default_factory=list)
110113
dir: pathlib.Path = pathlib.Path()
111114
imports: List[str] = field(default_factory=list)
112115
import_prefix: Optional[str] = None
@@ -148,7 +151,7 @@ def db_id(self) -> str:
148151

149152
@property
150153
def has_children(self) -> bool:
151-
return any(p.is_child for p in self.properties)
154+
return bool(self.all_children)
152155

153156
@property
154157
def last_base(self) -> str:

misc/codegen/templates/ql_parent.mustache

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,39 @@ import {{.}}
99

1010
private module Impl {
1111
{{#classes}}
12-
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
13-
{{! avoid unused argument warnings on root element, assuming the root element has no children }}
14-
{{#root}}none(){{/root}}
15-
{{^root}}
16-
{{! b is the base offset 0, for ease of generation }}
17-
{{! b<base> is constructed to be strictly greater than the indexes required for children coming from <base> }}
18-
{{! n is the base offset for direct children, equal to the last base offset from above }}
19-
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
20-
exists(int b{{#bases}}, int b{{.}}{{/bases}}, int n{{#properties}}{{#is_child}}, int n{{singular}}{{/is_child}}{{/properties}} |
21-
b = 0
22-
{{#bases}}
23-
and
24-
b{{.}} = b{{prev}} + 1 + max(int i | i = -1 or exists(getImmediateChildOf{{.}}(e, i, _)) | i)
25-
{{/bases}}
26-
and
27-
n = b{{last_base}}
28-
{{#properties}}
29-
{{#is_child}}
30-
{{! n<child> is defined on top of the previous definition }}
31-
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
32-
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
33-
and
34-
n{{singular}} = n{{prev_child}} + 1{{#is_repeated}}+ max(int i | i = -1 or exists(e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(i)) | i){{/is_repeated}}
35-
{{/is_child}}
36-
{{/properties}} and (
37-
none()
38-
{{#bases}}
39-
or
40-
result = getImmediateChildOf{{.}}(e, index - b{{prev}}, partialPredicateCall)
41-
{{/bases}}
42-
{{#properties}}
43-
{{#is_child}}
44-
or
45-
{{#is_repeated}}
46-
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev_child}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev_child}}).toString() + ")"
47-
{{/is_repeated}}
48-
{{^is_repeated}}
49-
index = n{{prev_child}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
50-
{{/is_repeated}}
51-
{{/is_child}}
52-
{{/properties}}
53-
))
54-
{{/root}}
55-
}
56-
12+
{{#final}}
13+
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
14+
{{^has_children}}none(){{/has_children}}
15+
{{#has_children}}
16+
{{! n is the base offset 0, for ease of generation }}
17+
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
18+
exists(int n{{#all_children}}, int n{{property.singular}}{{/all_children}} |
19+
n = 0
20+
{{#all_children}}
21+
{{#property}}
22+
{{! n<child> is defined on top of the previous definition }}
23+
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
24+
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
25+
and
26+
n{{singular}} = n{{prev}} + 1{{#is_repeated}}+ max(int i | i = -1 or exists(e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(i)) | i){{/is_repeated}}
27+
{{/property}}
28+
{{/all_children}} and (
29+
none()
30+
{{#all_children}}
31+
{{#property}}
32+
or
33+
{{#is_repeated}}
34+
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev}}).toString() + ")"
35+
{{/is_repeated}}
36+
{{^is_repeated}}
37+
index = n{{prev}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
38+
{{/is_repeated}}
39+
{{/property}}
40+
{{/all_children}}
41+
))
42+
{{/has_children}}
43+
}
44+
{{/final}}
5745
{{/classes}}
5846
cached
5947
Element getImmediateChild(Element e, int index, string partialAccessor) {

misc/codegen/test/test_ql.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,34 +133,18 @@ def test_non_root_class():
133133
assert not cls.root
134134

135135

136-
@pytest.mark.parametrize(
137-
"prev_child,is_child", [(None, False), ("", True), ("x", True)]
138-
)
139-
def test_is_child(prev_child, is_child):
140-
p = ql.Property("Foo", "int", prev_child=prev_child)
141-
assert p.is_child is is_child
142-
143-
144-
def test_empty_class_no_children():
145-
cls = ql.Class("Class", properties=[])
146-
assert cls.has_children is False
147-
148-
149136
def test_class_no_children():
150137
cls = ql.Class(
151-
"Class", properties=[ql.Property("Foo", "int"), ql.Property("Bar", "string")]
138+
"Class",
139+
all_children=[],
152140
)
153141
assert cls.has_children is False
154142

155143

156144
def test_class_with_children():
157145
cls = ql.Class(
158146
"Class",
159-
properties=[
160-
ql.Property("Foo", "int"),
161-
ql.Property("Child", "x", prev_child=""),
162-
ql.Property("Bar", "string"),
163-
],
147+
all_children=[ql.Child(ql.Property("Foo", "int"))],
164148
)
165149
assert cls.has_children is True
166150

0 commit comments

Comments
 (0)