Skip to content
Merged
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
8 changes: 1 addition & 7 deletions swift/codegen/generators/cppgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,10 @@ def _get_class(self, name: str) -> cpp.Class:
trap_name=trap_name,
)

@functools.lru_cache(maxsize=None)
def _is_ipa(self, name: str) -> bool:
cls = self._classmap[name]
return cls.ipa is not None or (
cls.derived and all(self._is_ipa(d) for d in cls.derived))

def get_classes(self):
ret = {'': []}
for k, cls in self._classmap.items():
if not self._is_ipa(k):
if not cls.ipa:
ret.setdefault(cls.group, []).append(self._get_class(cls.name))
return ret

Expand Down
8 changes: 5 additions & 3 deletions swift/codegen/generators/dbschemegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ def dbtype(typename: str, add_or_none_except: typing.Optional[str] = None) -> st
return typename


def cls_to_dbscheme(cls: schema.Class, add_or_none_except: typing.Optional[str] = None):
def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], add_or_none_except: typing.Optional[str] = None):
""" Yield all dbscheme entities needed to model class `cls` """
if cls.ipa:
return
if cls.derived:
yield Union(dbtype(cls.name), (dbtype(c) for c in cls.derived))
yield Union(dbtype(cls.name), (dbtype(c) for c in cls.derived if not lookup[c].ipa))
dir = pathlib.Path(cls.group) if cls.group else None
# output a table specific to a class only if it is a leaf class or it has 1-to-1 properties
# Leaf classes need a table to bind the `@` ids
Expand Down Expand Up @@ -96,7 +98,7 @@ def cls_to_dbscheme(cls: schema.Class, add_or_none_except: typing.Optional[str]

def get_declarations(data: schema.Schema):
add_or_none_except = data.root_class.name if data.null else None
declarations = [d for cls in data.classes.values() for d in cls_to_dbscheme(cls, add_or_none_except)]
declarations = [d for cls in data.classes.values() for d in cls_to_dbscheme(cls, data.classes, add_or_none_except)]
if data.null:
property_classes = {
prop.type for cls in data.classes.values() for prop in cls.properties
Expand Down
35 changes: 34 additions & 1 deletion swift/codegen/lib/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class Class:
properties: List[Property] = field(default_factory=list)
group: str = ""
pragmas: List[str] = field(default_factory=list)
ipa: Optional[IpaInfo] = None
ipa: Optional[Union[IpaInfo, bool]] = None
"""^^^ filled with `True` for non-final classes with only synthesized final descendants """
doc: List[str] = field(default_factory=list)
default_doc_name: Optional[str] = None

Expand Down Expand Up @@ -247,6 +248,36 @@ def _toposort_classes_by_group(classes: typing.Dict[str, Class]) -> typing.Dict[
return ret


def _fill_ipa_information(classes: typing.Dict[str, Class]):
""" Take a dictionary where the `ipa` field is filled for all explicitly synthesized classes
and update it so that all non-final classes that have only synthesized final descendants
get `True` as` value for the `ipa` field
"""
if not classes:
return

is_ipa: typing.Dict[str, bool] = {}

def fill_is_ipa(name: str):
if name not in is_ipa:
cls = classes[name]
for d in cls.derived:
fill_is_ipa(d)
if cls.ipa is not None:
is_ipa[name] = True
elif not cls.derived:
is_ipa[name] = False
else:
is_ipa[name] = all(is_ipa[d] for d in cls.derived)

root = next(iter(classes))
fill_is_ipa(root)

for name, cls in classes.items():
if cls.ipa is None and is_ipa[name]:
cls.ipa = True


def load(m: types.ModuleType) -> Schema:
includes = set()
classes = {}
Expand Down Expand Up @@ -274,6 +305,8 @@ def load(m: types.ModuleType) -> Schema:
null = name
cls.is_null_class = True

_fill_ipa_information(classes)

return Schema(includes=includes, classes=_toposort_classes_by_group(classes), null=null)


Expand Down
63 changes: 3 additions & 60 deletions swift/codegen/test/test_cppgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,79 +183,22 @@ def test_cpp_skip_pragma(generate):
def test_ipa_classes_ignored(generate):
assert generate([
schema.Class(
name="X",
ipa=schema.IpaInfo(from_class="A"),
),
schema.Class(
name="Y",
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
),
schema.Class(
name="Z",
),
]) == [
cpp.Class(name="Z", final=True, trap_name="Zs"),
]


def test_ipa_hierarchy_ignored(generate):
assert generate([
schema.Class(
name="Root",
derived={"Base", "Z"},
),
schema.Class(
name="Base",
bases=["Root"],
derived={"X", "Y"}
name="W",
ipa=schema.IpaInfo(),
),
schema.Class(
name="X",
bases=["Base"],
ipa=schema.IpaInfo(from_class="A"),
),
schema.Class(
name="Y",
bases=["Base"],
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
),
schema.Class(
name="Z",
ipa=schema.IpaInfo(from_class="A"),
),
]) == []


def test_ipa_hierarchy_not_ignored_with_non_ipa_descendant(generate):
root = cpp.Class(name="Root")
base = cpp.Class(name="Base", bases=[root])
assert generate([
schema.Class(
name="Root",
derived={"Base", "Z"},
),
schema.Class(
name="Base",
bases=["Root"],
derived={"X", "Y"}
),
schema.Class(
name="X",
bases=["Base"],
),
schema.Class(
name="Y",
bases=["Base"],
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
),
schema.Class(
name="Z",
ipa=schema.IpaInfo(from_class="A"),
),
]) == [
root,
base,
cpp.Class(name="X", bases=[base], final=True, trap_name="Xes"),
cpp.Class(name="Z", final=True, trap_name="Zs"),
]


Expand Down
81 changes: 76 additions & 5 deletions swift/codegen/test/test_dbschemegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,9 @@ def test_final_class_with_more_fields(generate, dir_param):

def test_empty_class_with_derived(generate):
assert generate([
schema.Class(
name="Base", derived={"Left", "Right"}),
schema.Class(name="Base", derived={"Left", "Right"}),
schema.Class(name="Left", bases=["Base"]),
schema.Class(name="Right", bases=["Base"]),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
Expand All @@ -265,6 +266,14 @@ def test_empty_class_with_derived(generate):
lhs="@base",
rhs=["@left", "@right"],
),
dbscheme.Table(
name="lefts",
columns=[dbscheme.Column("id", "@left", binding=True)],
),
dbscheme.Table(
name="rights",
columns=[dbscheme.Column("id", "@right", binding=True)],
),
],
)

Expand All @@ -278,6 +287,8 @@ def test_class_with_derived_and_single_property(generate, dir_param):
properties=[
schema.SingleProperty("single", "Prop"),
]),
schema.Class(name="Left", bases=["Base"]),
schema.Class(name="Right", bases=["Base"]),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
Expand All @@ -294,7 +305,15 @@ def test_class_with_derived_and_single_property(generate, dir_param):
dbscheme.Column('single', '@prop'),
],
dir=dir_param.expected,
)
),
dbscheme.Table(
name="lefts",
columns=[dbscheme.Column("id", "@left", binding=True)],
),
dbscheme.Table(
name="rights",
columns=[dbscheme.Column("id", "@right", binding=True)],
),
],
)

Expand All @@ -308,6 +327,8 @@ def test_class_with_derived_and_optional_property(generate, dir_param):
properties=[
schema.OptionalProperty("opt", "Prop"),
]),
schema.Class(name="Left", bases=["Base"]),
schema.Class(name="Right", bases=["Base"]),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
Expand All @@ -324,7 +345,15 @@ def test_class_with_derived_and_optional_property(generate, dir_param):
dbscheme.Column('opt', '@prop'),
],
dir=dir_param.expected,
)
),
dbscheme.Table(
name="lefts",
columns=[dbscheme.Column("id", "@left", binding=True)],
),
dbscheme.Table(
name="rights",
columns=[dbscheme.Column("id", "@right", binding=True)],
),
],
)

Expand All @@ -338,6 +367,8 @@ def test_class_with_derived_and_repeated_property(generate, dir_param):
properties=[
schema.RepeatedProperty("rep", "Prop"),
]),
schema.Class(name="Left", bases=["Base"]),
schema.Class(name="Right", bases=["Base"]),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
Expand All @@ -355,7 +386,15 @@ def test_class_with_derived_and_repeated_property(generate, dir_param):
dbscheme.Column('rep', '@prop'),
],
dir=dir_param.expected,
)
),
dbscheme.Table(
name="lefts",
columns=[dbscheme.Column("id", "@left", binding=True)],
),
dbscheme.Table(
name="rights",
columns=[dbscheme.Column("id", "@right", binding=True)],
),
],
)

Expand Down Expand Up @@ -469,5 +508,37 @@ def test_null_class(generate):
)


def test_ipa_classes_ignored(generate):
assert generate([
schema.Class(name="A", ipa=schema.IpaInfo()),
schema.Class(name="B", ipa=schema.IpaInfo(from_class="A")),
schema.Class(name="C", ipa=schema.IpaInfo(on_arguments={"x": "A"})),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
declarations=[],
)


def test_ipa_derived_classes_ignored(generate):
assert generate([
schema.Class(name="A", derived={"B", "C"}),
schema.Class(name="B", bases=["A"], ipa=schema.IpaInfo()),
schema.Class(name="C", bases=["A"]),
]) == dbscheme.Scheme(
src=schema_file,
includes=[],
declarations=[
dbscheme.Union("@a", ["@c"]),
dbscheme.Table(
name="cs",
columns=[
dbscheme.Column("id", "@c", binding=True),
],
)
],
)


if __name__ == '__main__':
sys.exit(pytest.main([__file__] + sys.argv[1:]))
37 changes: 35 additions & 2 deletions swift/codegen/test/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class B(A):
pass

assert data.classes == {
'A': schema.Class('A', derived={'B'}),
'A': schema.Class('A', derived={'B'}, ipa=True),
'B': schema.Class('B', bases=['A'], ipa=schema.IpaInfo(from_class="A")),
}

Expand Down Expand Up @@ -381,7 +381,7 @@ class B(A):
pass

assert data.classes == {
'A': schema.Class('A', derived={'B'}),
'A': schema.Class('A', derived={'B'}, ipa=True),
'B': schema.Class('B', bases=['A'], ipa=schema.IpaInfo(on_arguments={'a': 'A', 'i': 'int'})),
}

Expand Down Expand Up @@ -414,6 +414,39 @@ class B:
pass


def test_ipa_class_hierarchy():
@schema.load
class data:
class Root:
pass

class Base(Root):
pass

class Intermediate(Base):
pass

@defs.synth.on_arguments(a=Base, i=defs.int)
class A(Intermediate):
pass

@defs.synth.from_class(Base)
class B(Base):
pass

class C(Root):
pass

assert data.classes == {
'Root': schema.Class('Root', derived={'Base', 'C'}),
'Base': schema.Class('Base', bases=['Root'], derived={'Intermediate', 'B'}, ipa=True),
'Intermediate': schema.Class('Intermediate', bases=['Base'], derived={'A'}, ipa=True),
'A': schema.Class('A', bases=['Intermediate'], ipa=schema.IpaInfo(on_arguments={'a': 'Base', 'i': 'int'})),
'B': schema.Class('B', bases=['Base'], ipa=schema.IpaInfo(from_class='Base')),
'C': schema.Class('C', bases=['Root']),
}


def test_class_docstring():
@schema.load
class data:
Expand Down
Loading