Skip to content

Commit 2796c46

Browse files
authored
Merge pull request github#11292 from github/redsun82/swift-remove-ipa-from-dbscheme-cpp
Swift: remove synthesized classes from the dbscheme
2 parents d6c5132 + 1c69a1f commit 2796c46

File tree

13 files changed

+10103
-93
lines changed

13 files changed

+10103
-93
lines changed

swift/codegen/generators/cppgen.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,10 @@ def _get_class(self, name: str) -> cpp.Class:
8080
trap_name=trap_name,
8181
)
8282

83-
@functools.lru_cache(maxsize=None)
84-
def _is_ipa(self, name: str) -> bool:
85-
cls = self._classmap[name]
86-
return cls.ipa is not None or (
87-
cls.derived and all(self._is_ipa(d) for d in cls.derived))
88-
8983
def get_classes(self):
9084
ret = {'': []}
9185
for k, cls in self._classmap.items():
92-
if not self._is_ipa(k):
86+
if not cls.ipa:
9387
ret.setdefault(cls.group, []).append(self._get_class(cls.name))
9488
return ret
9589

swift/codegen/generators/dbschemegen.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ def dbtype(typename: str, add_or_none_except: typing.Optional[str] = None) -> st
3838
return typename
3939

4040

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

9799
def get_declarations(data: schema.Schema):
98100
add_or_none_except = data.root_class.name if data.null else None
99-
declarations = [d for cls in data.classes.values() for d in cls_to_dbscheme(cls, add_or_none_except)]
101+
declarations = [d for cls in data.classes.values() for d in cls_to_dbscheme(cls, data.classes, add_or_none_except)]
100102
if data.null:
101103
property_classes = {
102104
prop.type for cls in data.classes.values() for prop in cls.properties

swift/codegen/lib/schema/schema.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class Class:
8686
properties: List[Property] = field(default_factory=list)
8787
group: str = ""
8888
pragmas: List[str] = field(default_factory=list)
89-
ipa: Optional[IpaInfo] = None
89+
ipa: Optional[Union[IpaInfo, bool]] = None
90+
"""^^^ filled with `True` for non-final classes with only synthesized final descendants """
9091
doc: List[str] = field(default_factory=list)
9192
default_doc_name: Optional[str] = None
9293

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

249250

251+
def _fill_ipa_information(classes: typing.Dict[str, Class]):
252+
""" Take a dictionary where the `ipa` field is filled for all explicitly synthesized classes
253+
and update it so that all non-final classes that have only synthesized final descendants
254+
get `True` as` value for the `ipa` field
255+
"""
256+
if not classes:
257+
return
258+
259+
is_ipa: typing.Dict[str, bool] = {}
260+
261+
def fill_is_ipa(name: str):
262+
if name not in is_ipa:
263+
cls = classes[name]
264+
for d in cls.derived:
265+
fill_is_ipa(d)
266+
if cls.ipa is not None:
267+
is_ipa[name] = True
268+
elif not cls.derived:
269+
is_ipa[name] = False
270+
else:
271+
is_ipa[name] = all(is_ipa[d] for d in cls.derived)
272+
273+
root = next(iter(classes))
274+
fill_is_ipa(root)
275+
276+
for name, cls in classes.items():
277+
if cls.ipa is None and is_ipa[name]:
278+
cls.ipa = True
279+
280+
250281
def load(m: types.ModuleType) -> Schema:
251282
includes = set()
252283
classes = {}
@@ -274,6 +305,8 @@ def load(m: types.ModuleType) -> Schema:
274305
null = name
275306
cls.is_null_class = True
276307

308+
_fill_ipa_information(classes)
309+
277310
return Schema(includes=includes, classes=_toposort_classes_by_group(classes), null=null)
278311

279312

swift/codegen/test/test_cppgen.py

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -183,79 +183,22 @@ def test_cpp_skip_pragma(generate):
183183
def test_ipa_classes_ignored(generate):
184184
assert generate([
185185
schema.Class(
186-
name="X",
187-
ipa=schema.IpaInfo(from_class="A"),
188-
),
189-
schema.Class(
190-
name="Y",
191-
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
192-
),
193-
schema.Class(
194-
name="Z",
195-
),
196-
]) == [
197-
cpp.Class(name="Z", final=True, trap_name="Zs"),
198-
]
199-
200-
201-
def test_ipa_hierarchy_ignored(generate):
202-
assert generate([
203-
schema.Class(
204-
name="Root",
205-
derived={"Base", "Z"},
206-
),
207-
schema.Class(
208-
name="Base",
209-
bases=["Root"],
210-
derived={"X", "Y"}
186+
name="W",
187+
ipa=schema.IpaInfo(),
211188
),
212189
schema.Class(
213190
name="X",
214-
bases=["Base"],
215-
ipa=schema.IpaInfo(from_class="A"),
216-
),
217-
schema.Class(
218-
name="Y",
219-
bases=["Base"],
220-
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
221-
),
222-
schema.Class(
223-
name="Z",
224191
ipa=schema.IpaInfo(from_class="A"),
225192
),
226-
]) == []
227-
228-
229-
def test_ipa_hierarchy_not_ignored_with_non_ipa_descendant(generate):
230-
root = cpp.Class(name="Root")
231-
base = cpp.Class(name="Base", bases=[root])
232-
assert generate([
233-
schema.Class(
234-
name="Root",
235-
derived={"Base", "Z"},
236-
),
237-
schema.Class(
238-
name="Base",
239-
bases=["Root"],
240-
derived={"X", "Y"}
241-
),
242-
schema.Class(
243-
name="X",
244-
bases=["Base"],
245-
),
246193
schema.Class(
247194
name="Y",
248-
bases=["Base"],
249195
ipa=schema.IpaInfo(on_arguments={"a": "A", "b": "int"}),
250196
),
251197
schema.Class(
252198
name="Z",
253-
ipa=schema.IpaInfo(from_class="A"),
254199
),
255200
]) == [
256-
root,
257-
base,
258-
cpp.Class(name="X", bases=[base], final=True, trap_name="Xes"),
201+
cpp.Class(name="Z", final=True, trap_name="Zs"),
259202
]
260203

261204

swift/codegen/test/test_dbschemegen.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,9 @@ def test_final_class_with_more_fields(generate, dir_param):
255255

256256
def test_empty_class_with_derived(generate):
257257
assert generate([
258-
schema.Class(
259-
name="Base", derived={"Left", "Right"}),
258+
schema.Class(name="Base", derived={"Left", "Right"}),
259+
schema.Class(name="Left", bases=["Base"]),
260+
schema.Class(name="Right", bases=["Base"]),
260261
]) == dbscheme.Scheme(
261262
src=schema_file,
262263
includes=[],
@@ -265,6 +266,14 @@ def test_empty_class_with_derived(generate):
265266
lhs="@base",
266267
rhs=["@left", "@right"],
267268
),
269+
dbscheme.Table(
270+
name="lefts",
271+
columns=[dbscheme.Column("id", "@left", binding=True)],
272+
),
273+
dbscheme.Table(
274+
name="rights",
275+
columns=[dbscheme.Column("id", "@right", binding=True)],
276+
),
268277
],
269278
)
270279

@@ -278,6 +287,8 @@ def test_class_with_derived_and_single_property(generate, dir_param):
278287
properties=[
279288
schema.SingleProperty("single", "Prop"),
280289
]),
290+
schema.Class(name="Left", bases=["Base"]),
291+
schema.Class(name="Right", bases=["Base"]),
281292
]) == dbscheme.Scheme(
282293
src=schema_file,
283294
includes=[],
@@ -294,7 +305,15 @@ def test_class_with_derived_and_single_property(generate, dir_param):
294305
dbscheme.Column('single', '@prop'),
295306
],
296307
dir=dir_param.expected,
297-
)
308+
),
309+
dbscheme.Table(
310+
name="lefts",
311+
columns=[dbscheme.Column("id", "@left", binding=True)],
312+
),
313+
dbscheme.Table(
314+
name="rights",
315+
columns=[dbscheme.Column("id", "@right", binding=True)],
316+
),
298317
],
299318
)
300319

@@ -308,6 +327,8 @@ def test_class_with_derived_and_optional_property(generate, dir_param):
308327
properties=[
309328
schema.OptionalProperty("opt", "Prop"),
310329
]),
330+
schema.Class(name="Left", bases=["Base"]),
331+
schema.Class(name="Right", bases=["Base"]),
311332
]) == dbscheme.Scheme(
312333
src=schema_file,
313334
includes=[],
@@ -324,7 +345,15 @@ def test_class_with_derived_and_optional_property(generate, dir_param):
324345
dbscheme.Column('opt', '@prop'),
325346
],
326347
dir=dir_param.expected,
327-
)
348+
),
349+
dbscheme.Table(
350+
name="lefts",
351+
columns=[dbscheme.Column("id", "@left", binding=True)],
352+
),
353+
dbscheme.Table(
354+
name="rights",
355+
columns=[dbscheme.Column("id", "@right", binding=True)],
356+
),
328357
],
329358
)
330359

@@ -338,6 +367,8 @@ def test_class_with_derived_and_repeated_property(generate, dir_param):
338367
properties=[
339368
schema.RepeatedProperty("rep", "Prop"),
340369
]),
370+
schema.Class(name="Left", bases=["Base"]),
371+
schema.Class(name="Right", bases=["Base"]),
341372
]) == dbscheme.Scheme(
342373
src=schema_file,
343374
includes=[],
@@ -355,7 +386,15 @@ def test_class_with_derived_and_repeated_property(generate, dir_param):
355386
dbscheme.Column('rep', '@prop'),
356387
],
357388
dir=dir_param.expected,
358-
)
389+
),
390+
dbscheme.Table(
391+
name="lefts",
392+
columns=[dbscheme.Column("id", "@left", binding=True)],
393+
),
394+
dbscheme.Table(
395+
name="rights",
396+
columns=[dbscheme.Column("id", "@right", binding=True)],
397+
),
359398
],
360399
)
361400

@@ -469,5 +508,37 @@ def test_null_class(generate):
469508
)
470509

471510

511+
def test_ipa_classes_ignored(generate):
512+
assert generate([
513+
schema.Class(name="A", ipa=schema.IpaInfo()),
514+
schema.Class(name="B", ipa=schema.IpaInfo(from_class="A")),
515+
schema.Class(name="C", ipa=schema.IpaInfo(on_arguments={"x": "A"})),
516+
]) == dbscheme.Scheme(
517+
src=schema_file,
518+
includes=[],
519+
declarations=[],
520+
)
521+
522+
523+
def test_ipa_derived_classes_ignored(generate):
524+
assert generate([
525+
schema.Class(name="A", derived={"B", "C"}),
526+
schema.Class(name="B", bases=["A"], ipa=schema.IpaInfo()),
527+
schema.Class(name="C", bases=["A"]),
528+
]) == dbscheme.Scheme(
529+
src=schema_file,
530+
includes=[],
531+
declarations=[
532+
dbscheme.Union("@a", ["@c"]),
533+
dbscheme.Table(
534+
name="cs",
535+
columns=[
536+
dbscheme.Column("id", "@c", binding=True),
537+
],
538+
)
539+
],
540+
)
541+
542+
472543
if __name__ == '__main__':
473544
sys.exit(pytest.main([__file__] + sys.argv[1:]))

swift/codegen/test/test_schema.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ class B(A):
340340
pass
341341

342342
assert data.classes == {
343-
'A': schema.Class('A', derived={'B'}),
343+
'A': schema.Class('A', derived={'B'}, ipa=True),
344344
'B': schema.Class('B', bases=['A'], ipa=schema.IpaInfo(from_class="A")),
345345
}
346346

@@ -381,7 +381,7 @@ class B(A):
381381
pass
382382

383383
assert data.classes == {
384-
'A': schema.Class('A', derived={'B'}),
384+
'A': schema.Class('A', derived={'B'}, ipa=True),
385385
'B': schema.Class('B', bases=['A'], ipa=schema.IpaInfo(on_arguments={'a': 'A', 'i': 'int'})),
386386
}
387387

@@ -414,6 +414,39 @@ class B:
414414
pass
415415

416416

417+
def test_ipa_class_hierarchy():
418+
@schema.load
419+
class data:
420+
class Root:
421+
pass
422+
423+
class Base(Root):
424+
pass
425+
426+
class Intermediate(Base):
427+
pass
428+
429+
@defs.synth.on_arguments(a=Base, i=defs.int)
430+
class A(Intermediate):
431+
pass
432+
433+
@defs.synth.from_class(Base)
434+
class B(Base):
435+
pass
436+
437+
class C(Root):
438+
pass
439+
440+
assert data.classes == {
441+
'Root': schema.Class('Root', derived={'Base', 'C'}),
442+
'Base': schema.Class('Base', bases=['Root'], derived={'Intermediate', 'B'}, ipa=True),
443+
'Intermediate': schema.Class('Intermediate', bases=['Base'], derived={'A'}, ipa=True),
444+
'A': schema.Class('A', bases=['Intermediate'], ipa=schema.IpaInfo(on_arguments={'a': 'Base', 'i': 'int'})),
445+
'B': schema.Class('B', bases=['Base'], ipa=schema.IpaInfo(from_class='Base')),
446+
'C': schema.Class('C', bases=['Root']),
447+
}
448+
449+
417450
def test_class_docstring():
418451
@schema.load
419452
class data:

0 commit comments

Comments
 (0)