Skip to content

Codegen: improve implementation of generated parent/child relationship #19866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 196 additions & 171 deletions misc/codegen/generators/qlgen.py

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions misc/codegen/generators/rusttestgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,12 @@ def generate(opts, renderer):
registry=opts.ql_test_output / ".generated_tests.list",
force=opts.force,
) as renderer:

resolver = qlgen.Resolver(schema.classes)
for cls in schema.classes.values():
if cls.imported:
continue
if (
qlgen.should_skip_qltest(cls, schema.classes)
or "rust_skip_doc_test" in cls.pragmas
):
if resolver.should_skip_qltest(cls) or "rust_skip_doc_test" in cls.pragmas:
continue
code = _get_code(cls.doc)
for p in schema.iter_properties(cls.name):
Expand Down
15 changes: 9 additions & 6 deletions misc/codegen/lib/ql.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class Property:
is_optional: bool = False
is_predicate: bool = False
is_unordered: bool = False
prev_child: Optional[str] = None
qltest_skip: bool = False
description: List[str] = field(default_factory=list)
doc: Optional[str] = None
Expand All @@ -48,6 +47,7 @@ class Property:
type_is_self: bool = False
internal: bool = False
cfg: bool = False
is_child: bool = False

def __post_init__(self):
if self.tableparams:
Expand Down Expand Up @@ -76,10 +76,6 @@ def is_repeated(self):
def is_single(self):
return not (self.is_optional or self.is_repeated or self.is_predicate)

@property
def is_child(self):
return self.prev_child is not None

@property
def is_indexed(self) -> bool:
return self.is_repeated and not self.is_unordered
Expand All @@ -89,6 +85,12 @@ def type_alias(self) -> Optional[str]:
return self.type + "Alias" if self.type_is_self else self.type


@dataclass
class Child:
property: Property
prev: str = ""


@dataclass
class Base:
base: str
Expand All @@ -107,6 +109,7 @@ class Class:
bases_impl: List[Base] = field(default_factory=list)
final: bool = False
properties: List[Property] = field(default_factory=list)
all_children: List[Child] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
imports: List[str] = field(default_factory=list)
import_prefix: Optional[str] = None
Expand Down Expand Up @@ -148,7 +151,7 @@ def db_id(self) -> str:

@property
def has_children(self) -> bool:
return any(p.is_child for p in self.properties)
return bool(self.all_children)

@property
def last_base(self) -> str:
Expand Down
78 changes: 33 additions & 45 deletions misc/codegen/templates/ql_parent.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,39 @@ import {{.}}

private module Impl {
{{#classes}}
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
{{! avoid unused argument warnings on root element, assuming the root element has no children }}
{{#root}}none(){{/root}}
{{^root}}
{{! b is the base offset 0, for ease of generation }}
{{! b<base> is constructed to be strictly greater than the indexes required for children coming from <base> }}
{{! n is the base offset for direct children, equal to the last base offset from above }}
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
exists(int b{{#bases}}, int b{{.}}{{/bases}}, int n{{#properties}}{{#is_child}}, int n{{singular}}{{/is_child}}{{/properties}} |
b = 0
{{#bases}}
and
b{{.}} = b{{prev}} + 1 + max(int i | i = -1 or exists(getImmediateChildOf{{.}}(e, i, _)) | i)
{{/bases}}
and
n = b{{last_base}}
{{#properties}}
{{#is_child}}
{{! n<child> is defined on top of the previous definition }}
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
and
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}}
{{/is_child}}
{{/properties}} and (
none()
{{#bases}}
or
result = getImmediateChildOf{{.}}(e, index - b{{prev}}, partialPredicateCall)
{{/bases}}
{{#properties}}
{{#is_child}}
or
{{#is_repeated}}
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev_child}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev_child}}).toString() + ")"
{{/is_repeated}}
{{^is_repeated}}
index = n{{prev_child}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
{{/is_repeated}}
{{/is_child}}
{{/properties}}
))
{{/root}}
}

{{#final}}
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
{{^has_children}}none(){{/has_children}}
{{#has_children}}
{{! n is the base offset 0, for ease of generation }}
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
exists(int n{{#all_children}}, int n{{property.singular}}{{/all_children}} |
n = 0
{{#all_children}}
{{#property}}
{{! n<child> is defined on top of the previous definition }}
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
and
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}}
{{/property}}
{{/all_children}} and (
none()
{{#all_children}}
{{#property}}
or
{{#is_repeated}}
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev}}).toString() + ")"
{{/is_repeated}}
{{^is_repeated}}
index = n{{prev}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
{{/is_repeated}}
{{/property}}
{{/all_children}}
))
{{/has_children}}
}
{{/final}}
{{/classes}}
cached
Element getImmediateChild(Element e, int index, string partialAccessor) {
Expand Down
22 changes: 3 additions & 19 deletions misc/codegen/test/test_ql.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,34 +133,18 @@ def test_non_root_class():
assert not cls.root


@pytest.mark.parametrize(
"prev_child,is_child", [(None, False), ("", True), ("x", True)]
)
def test_is_child(prev_child, is_child):
p = ql.Property("Foo", "int", prev_child=prev_child)
assert p.is_child is is_child


def test_empty_class_no_children():
cls = ql.Class("Class", properties=[])
assert cls.has_children is False


def test_class_no_children():
cls = ql.Class(
"Class", properties=[ql.Property("Foo", "int"), ql.Property("Bar", "string")]
"Class",
all_children=[],
)
assert cls.has_children is False


def test_class_with_children():
cls = ql.Class(
"Class",
properties=[
ql.Property("Foo", "int"),
ql.Property("Child", "x", prev_child=""),
ql.Property("Bar", "string"),
],
all_children=[ql.Child(ql.Property("Foo", "int"))],
)
assert cls.has_children is True

Expand Down
Loading
Loading