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.
This commit is contained in:
Paolo Tranquilli
2025-06-24 17:26:24 +02:00
parent c4a385fa6a
commit 1dcd60527c
19 changed files with 2120 additions and 5238 deletions

View File

@@ -133,22 +133,10 @@ 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
@@ -156,11 +144,7 @@ def test_class_no_children():
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

View File

@@ -388,11 +388,101 @@ def test_internal_property(generate_classes):
def test_children(generate_classes):
expected_parent_property = ql.Property(
singular="ParentChild",
type="int",
is_child=True,
tablename="parents",
tableparams=["this", "result"],
doc="parent child of this parent",
)
expected_properties = [
ql.Property(
singular="A",
type="int",
tablename="my_objects",
tableparams=["this", "result", "_"],
doc="a of this my object",
),
ql.Property(
singular="Child1",
type="int",
tablename="my_objects",
tableparams=["this", "_", "result"],
is_child=True,
doc="child 1 of this my object",
),
ql.Property(
singular="B",
plural="Bs",
type="int",
tablename="my_object_bs",
tableparams=["this", "index", "result"],
doc="b of this my object",
doc_plural="bs of this my object",
),
ql.Property(
singular="Child",
plural="Children",
type="int",
tablename="my_object_children",
tableparams=["this", "index", "result"],
is_child=True,
doc="child of this my object",
doc_plural="children of this my object",
),
ql.Property(
singular="C",
type="int",
tablename="my_object_cs",
tableparams=["this", "result"],
is_optional=True,
doc="c of this my object",
),
ql.Property(
singular="Child3",
type="int",
tablename="my_object_child_3s",
tableparams=["this", "result"],
is_optional=True,
is_child=True,
doc="child 3 of this my object",
),
ql.Property(
singular="D",
plural="Ds",
type="int",
tablename="my_object_ds",
tableparams=["this", "index", "result"],
is_optional=True,
doc="d of this my object",
doc_plural="ds of this my object",
),
ql.Property(
singular="Child4",
plural="Child4s",
type="int",
tablename="my_object_child_4s",
tableparams=["this", "index", "result"],
is_optional=True,
is_child=True,
doc="child 4 of this my object",
doc_plural="child 4s of this my object",
),
]
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"Parent",
derived={"MyObject"},
properties=[
schema.SingleProperty("parent_child", "int", is_child=True),
],
),
schema.Class(
"MyObject",
bases=["Parent"],
properties=[
schema.SingleProperty("a", "int"),
schema.SingleProperty("child_1", "int", is_child=True),
@@ -413,87 +503,53 @@ def test_children(generate_classes):
name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"]
),
),
"Parent.qll": (
a_ql_class_public(name="Parent"),
a_ql_stub(name="Parent"),
a_ql_class(
name="Parent",
imports=[stub_import_prefix + "Parent"],
properties=[expected_parent_property],
all_children=[
ql.Child(
expected_parent_property,
),
],
),
),
"MyObject.qll": (
a_ql_class_public(name="MyObject"),
a_ql_class_public(name="MyObject", imports=[stub_import_prefix + "Parent"]),
a_ql_stub(name="MyObject"),
a_ql_class(
name="MyObject",
final=True,
properties=[
ql.Property(
singular="A",
type="int",
tablename="my_objects",
tableparams=["this", "result", "_"],
doc="a of this my object",
bases=["Parent"],
bases_impl=["ParentImpl::Parent"],
properties=expected_properties,
all_children=[
ql.Child(
expected_parent_property,
),
ql.Property(
singular="Child1",
type="int",
tablename="my_objects",
tableparams=["this", "_", "result"],
prev_child="",
doc="child 1 of this my object",
ql.Child(
expected_properties[1],
prev="ParentChild",
),
ql.Property(
singular="B",
plural="Bs",
type="int",
tablename="my_object_bs",
tableparams=["this", "index", "result"],
doc="b of this my object",
doc_plural="bs of this my object",
ql.Child(
expected_properties[3],
prev="Child1",
),
ql.Property(
singular="Child",
plural="Children",
type="int",
tablename="my_object_children",
tableparams=["this", "index", "result"],
prev_child="Child1",
doc="child of this my object",
doc_plural="children of this my object",
ql.Child(
expected_properties[5],
prev="Child",
),
ql.Property(
singular="C",
type="int",
tablename="my_object_cs",
tableparams=["this", "result"],
is_optional=True,
doc="c of this my object",
),
ql.Property(
singular="Child3",
type="int",
tablename="my_object_child_3s",
tableparams=["this", "result"],
is_optional=True,
prev_child="Child",
doc="child 3 of this my object",
),
ql.Property(
singular="D",
plural="Ds",
type="int",
tablename="my_object_ds",
tableparams=["this", "index", "result"],
is_optional=True,
doc="d of this my object",
doc_plural="ds of this my object",
),
ql.Property(
singular="Child4",
plural="Child4s",
type="int",
tablename="my_object_child_4s",
tableparams=["this", "index", "result"],
is_optional=True,
prev_child="Child3",
doc="child 4 of this my object",
doc_plural="child 4s of this my object",
ql.Child(
expected_properties[7],
prev="Child3",
),
],
imports=[stub_import_prefix + "MyObject"],
imports=[
stub_import_prefix + "internal.ParentImpl::Impl as ParentImpl"
],
),
),
}
@@ -547,14 +603,13 @@ def test_single_properties(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_optional_property(generate_classes, is_child, prev_child):
def test_optional_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[schema.OptionalProperty("foo", "bar", is_child=is_child)],
properties=[schema.OptionalProperty("foo", "bar")],
),
]
) == {
@@ -578,7 +633,6 @@ def test_optional_property(generate_classes, is_child, prev_child):
tablename="my_object_foos",
tableparams=["this", "result"],
is_optional=True,
prev_child=prev_child,
doc="foo of this my object",
),
],
@@ -588,14 +642,13 @@ def test_optional_property(generate_classes, is_child, prev_child):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_repeated_property(generate_classes, is_child, prev_child):
def test_repeated_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[schema.RepeatedProperty("foo", "bar", is_child=is_child)],
properties=[schema.RepeatedProperty("foo", "bar")],
),
]
) == {
@@ -619,7 +672,6 @@ def test_repeated_property(generate_classes, is_child, prev_child):
type="bar",
tablename="my_object_foos",
tableparams=["this", "index", "result"],
prev_child=prev_child,
doc="foo of this my object",
doc_plural="foos of this my object",
),
@@ -670,16 +722,13 @@ def test_repeated_unordered_property(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_repeated_optional_property(generate_classes, is_child, prev_child):
def test_repeated_optional_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[
schema.RepeatedOptionalProperty("foo", "bar", is_child=is_child)
],
properties=[schema.RepeatedOptionalProperty("foo", "bar")],
),
]
) == {
@@ -704,7 +753,6 @@ def test_repeated_optional_property(generate_classes, is_child, prev_child):
tablename="my_object_foos",
tableparams=["this", "index", "result"],
is_optional=True,
prev_child=prev_child,
doc="foo of this my object",
doc_plural="foos of this my object",
),
@@ -743,14 +791,13 @@ def test_predicate_property(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_single_class_property(generate_classes, is_child, prev_child):
def test_single_class_property(generate_classes):
assert generate_classes(
[
schema.Class("Bar"),
schema.Class(
"MyObject",
properties=[schema.SingleProperty("foo", "Bar", is_child=is_child)],
properties=[schema.SingleProperty("foo", "Bar")],
),
]
) == {
@@ -767,7 +814,6 @@ def test_single_class_property(generate_classes, is_child, prev_child):
type="Bar",
tablename="my_objects",
tableparams=["this", "result"],
prev_child=prev_child,
doc="foo of this my object",
type_is_codegen_class=True,
),