Skip to content

Commit 357542a

Browse files
authored
Merge pull request github#13258 from github/redsun82/swift-synth-properties
Codegen: allow `synth` properties of non-`synth` classes
2 parents aa8878b + 700e3d5 commit 357542a

17 files changed

+246
-153
lines changed

misc/codegen/generators/cppgen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def _get_class(self, name: str) -> cpp.Class:
7676
bases=[self._get_class(b) for b in cls.bases],
7777
fields=[
7878
_get_field(cls, p, self._add_or_none_except)
79-
for p in cls.properties if "cpp_skip" not in p.pragmas
79+
for p in cls.properties if "cpp_skip" not in p.pragmas and not p.synth
8080
],
8181
final=not cls.derived,
8282
trap_name=trap_name,
@@ -85,7 +85,7 @@ def _get_class(self, name: str) -> cpp.Class:
8585
def get_classes(self):
8686
ret = {'': []}
8787
for k, cls in self._classmap.items():
88-
if not cls.ipa:
88+
if not cls.synth:
8989
ret.setdefault(cls.group, []).append(self._get_class(cls.name))
9090
return ret
9191

misc/codegen/generators/dbschemegen.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ def dbtype(typename: str, add_or_none_except: typing.Optional[str] = None) -> st
4040

4141
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:
43+
if cls.synth:
4444
return
4545
if cls.derived:
46-
yield Union(dbtype(cls.name), (dbtype(c) for c in cls.derived if not lookup[c].ipa))
46+
yield Union(dbtype(cls.name), (dbtype(c) for c in cls.derived if not lookup[c].synth))
4747
dir = pathlib.Path(cls.group) if cls.group else None
4848
# output a table specific to a class only if it is a leaf class or it has 1-to-1 properties
4949
# Leaf classes need a table to bind the `@` ids
5050
# 1-to-1 properties are added to a class specific table
5151
# in other cases, separate tables are used for the properties, and a class specific table is unneeded
52-
if not cls.derived or any(f.is_single for f in cls.properties):
52+
if not cls.derived or any(f.is_single and not f.synth for f in cls.properties):
5353
binding = not cls.derived
5454
keyset = KeySet(["id"]) if cls.derived else None
5555
yield Table(
@@ -58,12 +58,15 @@ def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], a
5858
columns=[
5959
Column("id", type=dbtype(cls.name), binding=binding),
6060
] + [
61-
Column(f.name, dbtype(f.type, add_or_none_except)) for f in cls.properties if f.is_single
61+
Column(f.name, dbtype(f.type, add_or_none_except))
62+
for f in cls.properties if f.is_single and not f.synth
6263
],
6364
dir=dir,
6465
)
6566
# use property-specific tables for 1-to-many and 1-to-at-most-1 properties
6667
for f in cls.properties:
68+
if f.synth:
69+
continue
6770
if f.is_unordered:
6871
yield Table(
6972
name=inflection.tableize(f"{cls.name}_{f.name}"),

misc/codegen/generators/qlgen.py

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def get_ql_property(cls: schema.Class, prop: schema.Property, lookup: typing.Dic
111111
is_predicate=prop.is_predicate,
112112
is_unordered=prop.is_unordered,
113113
description=prop.description,
114+
synth=bool(cls.synth) or prop.synth,
114115
type_is_hideable=lookup[prop.type].hideable if prop.type in lookup else False,
115116
)
116117
if prop.is_single:
@@ -163,7 +164,6 @@ def get_ql_class(cls: schema.Class, lookup: typing.Dict[str, schema.Class]) -> q
163164
final=not cls.derived,
164165
properties=properties,
165166
dir=pathlib.Path(cls.group or ""),
166-
ipa=bool(cls.ipa),
167167
doc=cls.doc,
168168
hideable=cls.hideable,
169169
**pragmas,
@@ -179,26 +179,26 @@ def _to_db_type(x: str) -> str:
179179
_final_db_class_lookup = {}
180180

181181

182-
def get_ql_ipa_class_db(name: str) -> ql.Synth.FinalClassDb:
182+
def get_ql_synth_class_db(name: str) -> ql.Synth.FinalClassDb:
183183
return _final_db_class_lookup.setdefault(name, ql.Synth.FinalClassDb(name=name,
184184
params=[
185185
ql.Synth.Param("id", _to_db_type(name))]))
186186

187187

188-
def get_ql_ipa_class(cls: schema.Class):
188+
def get_ql_synth_class(cls: schema.Class):
189189
if cls.derived:
190190
return ql.Synth.NonFinalClass(name=cls.name, derived=sorted(cls.derived),
191191
root=not cls.bases)
192-
if cls.ipa and cls.ipa.from_class is not None:
193-
source = cls.ipa.from_class
194-
get_ql_ipa_class_db(source).subtract_type(cls.name)
195-
return ql.Synth.FinalClassDerivedIpa(name=cls.name,
196-
params=[ql.Synth.Param("id", _to_db_type(source))])
197-
if cls.ipa and cls.ipa.on_arguments is not None:
198-
return ql.Synth.FinalClassFreshIpa(name=cls.name,
199-
params=[ql.Synth.Param(k, _to_db_type(v))
200-
for k, v in cls.ipa.on_arguments.items()])
201-
return get_ql_ipa_class_db(cls.name)
192+
if cls.synth and cls.synth.from_class is not None:
193+
source = cls.synth.from_class
194+
get_ql_synth_class_db(source).subtract_type(cls.name)
195+
return ql.Synth.FinalClassDerivedSynth(name=cls.name,
196+
params=[ql.Synth.Param("id", _to_db_type(source))])
197+
if cls.synth and cls.synth.on_arguments is not None:
198+
return ql.Synth.FinalClassFreshSynth(name=cls.name,
199+
params=[ql.Synth.Param(k, _to_db_type(v))
200+
for k, v in cls.synth.on_arguments.items()])
201+
return get_ql_synth_class_db(cls.name)
202202

203203

204204
def get_import(file: pathlib.Path, root_dir: pathlib.Path):
@@ -291,26 +291,26 @@ def _should_skip_qltest(cls: schema.Class, lookup: typing.Dict[str, schema.Class
291291

292292

293293
def _get_stub(cls: schema.Class, base_import: str, generated_import_prefix: str) -> ql.Stub:
294-
if isinstance(cls.ipa, schema.IpaInfo):
295-
if cls.ipa.from_class is not None:
294+
if isinstance(cls.synth, schema.SynthInfo):
295+
if cls.synth.from_class is not None:
296296
accessors = [
297-
ql.IpaUnderlyingAccessor(
297+
ql.SynthUnderlyingAccessor(
298298
argument="Entity",
299-
type=_to_db_type(cls.ipa.from_class),
299+
type=_to_db_type(cls.synth.from_class),
300300
constructorparams=["result"]
301301
)
302302
]
303-
elif cls.ipa.on_arguments is not None:
303+
elif cls.synth.on_arguments is not None:
304304
accessors = [
305-
ql.IpaUnderlyingAccessor(
305+
ql.SynthUnderlyingAccessor(
306306
argument=inflection.camelize(arg),
307307
type=_to_db_type(type),
308-
constructorparams=["result" if a == arg else "_" for a in cls.ipa.on_arguments]
309-
) for arg, type in cls.ipa.on_arguments.items()
308+
constructorparams=["result" if a == arg else "_" for a in cls.synth.on_arguments]
309+
) for arg, type in cls.synth.on_arguments.items()
310310
]
311311
else:
312312
accessors = []
313-
return ql.Stub(name=cls.name, base_import=base_import, import_prefix=generated_import_prefix, ipa_accessors=accessors)
313+
return ql.Stub(name=cls.name, base_import=base_import, import_prefix=generated_import_prefix, synth_accessors=accessors)
314314

315315

316316
def generate(opts, renderer):
@@ -344,7 +344,7 @@ def generate(opts, renderer):
344344
with renderer.manage(generated=generated, stubs=stubs, registry=opts.generated_registry,
345345
force=opts.force) as renderer:
346346

347-
db_classes = [cls for cls in classes.values() if not cls.ipa]
347+
db_classes = [cls for name, cls in classes.items() if not data.classes[name].synth]
348348
renderer.render(ql.DbClasses(db_classes), out / "Raw.qll")
349349

350350
classes_by_dir_and_name = sorted(classes.values(), key=lambda cls: (cls.dir, cls.name))
@@ -401,32 +401,32 @@ def generate(opts, renderer):
401401
elements_module=elements_module,
402402
property=p), test_dir / f"{c.name}_{p.getter}.ql")
403403

404-
final_ipa_types = []
405-
non_final_ipa_types = []
404+
final_synth_types = []
405+
non_final_synth_types = []
406406
constructor_imports = []
407-
ipa_constructor_imports = []
407+
synth_constructor_imports = []
408408
stubs = {}
409409
for cls in sorted(data.classes.values(), key=lambda cls: (cls.group, cls.name)):
410-
ipa_type = get_ql_ipa_class(cls)
411-
if ipa_type.is_final:
412-
final_ipa_types.append(ipa_type)
413-
if ipa_type.has_params:
410+
synth_type = get_ql_synth_class(cls)
411+
if synth_type.is_final:
412+
final_synth_types.append(synth_type)
413+
if synth_type.has_params:
414414
stub_file = stub_out / cls.group / f"{cls.name}Constructor.qll"
415415
if not renderer.is_customized_stub(stub_file):
416-
# stub rendering must be postponed as we might not have yet all subtracted ipa types in `ipa_type`
417-
stubs[stub_file] = ql.Synth.ConstructorStub(ipa_type, import_prefix=generated_import_prefix)
416+
# stub rendering must be postponed as we might not have yet all subtracted synth types in `synth_type`
417+
stubs[stub_file] = ql.Synth.ConstructorStub(synth_type, import_prefix=generated_import_prefix)
418418
constructor_import = get_import(stub_file, opts.root_dir)
419419
constructor_imports.append(constructor_import)
420-
if ipa_type.is_ipa:
421-
ipa_constructor_imports.append(constructor_import)
420+
if synth_type.is_synth:
421+
synth_constructor_imports.append(constructor_import)
422422
else:
423-
non_final_ipa_types.append(ipa_type)
423+
non_final_synth_types.append(synth_type)
424424

425425
for stub_file, data in stubs.items():
426426
renderer.render(data, stub_file)
427427
renderer.render(ql.Synth.Types(root.name, generated_import_prefix,
428-
final_ipa_types, non_final_ipa_types), out / "Synth.qll")
428+
final_synth_types, non_final_synth_types), out / "Synth.qll")
429429
renderer.render(ql.ImportList(constructor_imports), out / "SynthConstructors.qll")
430-
renderer.render(ql.ImportList(ipa_constructor_imports), out / "PureSynthConstructors.qll")
430+
renderer.render(ql.ImportList(synth_constructor_imports), out / "PureSynthConstructors.qll")
431431
if opts.ql_format:
432432
format(opts.codeql_binary, renderer.written)

misc/codegen/lib/ql.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Property:
4242
description: List[str] = field(default_factory=list)
4343
doc: Optional[str] = None
4444
doc_plural: Optional[str] = None
45+
synth: bool = False
4546
type_is_hideable: bool = False
4647

4748
def __post_init__(self):
@@ -112,7 +113,6 @@ class Class:
112113
qltest_collapse_hierarchy: bool = False
113114
qltest_uncollapse_hierarchy: bool = False
114115
ql_internal: bool = False
115-
ipa: bool = False
116116
doc: List[str] = field(default_factory=list)
117117
hideable: bool = False
118118

@@ -147,7 +147,7 @@ def has_doc(self) -> bool:
147147

148148

149149
@dataclass
150-
class IpaUnderlyingAccessor:
150+
class SynthUnderlyingAccessor:
151151
argument: str
152152
type: str
153153
constructorparams: List[Param]
@@ -165,11 +165,11 @@ class Stub:
165165
name: str
166166
base_import: str
167167
import_prefix: str
168-
ipa_accessors: List[IpaUnderlyingAccessor] = field(default_factory=list)
168+
synth_accessors: List[SynthUnderlyingAccessor] = field(default_factory=list)
169169

170170
@property
171-
def has_ipa_accessors(self) -> bool:
172-
return bool(self.ipa_accessors)
171+
def has_synth_accessors(self) -> bool:
172+
return bool(self.synth_accessors)
173173

174174

175175
@dataclass
@@ -245,8 +245,8 @@ class Param:
245245
@dataclass
246246
class FinalClass(Class):
247247
is_final: ClassVar = True
248-
is_derived_ipa: ClassVar = False
249-
is_fresh_ipa: ClassVar = False
248+
is_derived_synth: ClassVar = False
249+
is_fresh_synth: ClassVar = False
250250
is_db: ClassVar = False
251251

252252
params: List["Synth.Param"] = field(default_factory=list)
@@ -256,37 +256,37 @@ def __post_init__(self):
256256
self.params[0].first = True
257257

258258
@property
259-
def is_ipa(self):
260-
return self.is_fresh_ipa or self.is_derived_ipa
259+
def is_synth(self):
260+
return self.is_fresh_synth or self.is_derived_synth
261261

262262
@property
263263
def has_params(self) -> bool:
264264
return bool(self.params)
265265

266266
@dataclass
267-
class FinalClassIpa(FinalClass):
267+
class FinalClassSynth(FinalClass):
268268
pass
269269

270270
@dataclass
271-
class FinalClassDerivedIpa(FinalClassIpa):
272-
is_derived_ipa: ClassVar = True
271+
class FinalClassDerivedSynth(FinalClassSynth):
272+
is_derived_synth: ClassVar = True
273273

274274
@dataclass
275-
class FinalClassFreshIpa(FinalClassIpa):
276-
is_fresh_ipa: ClassVar = True
275+
class FinalClassFreshSynth(FinalClassSynth):
276+
is_fresh_synth: ClassVar = True
277277

278278
@dataclass
279279
class FinalClassDb(FinalClass):
280280
is_db: ClassVar = True
281281

282-
subtracted_ipa_types: List["Synth.Class"] = field(default_factory=list)
282+
subtracted_synth_types: List["Synth.Class"] = field(default_factory=list)
283283

284284
def subtract_type(self, type: str):
285-
self.subtracted_ipa_types.append(Synth.Class(type, first=not self.subtracted_ipa_types))
285+
self.subtracted_synth_types.append(Synth.Class(type, first=not self.subtracted_synth_types))
286286

287287
@property
288-
def has_subtracted_ipa_types(self) -> bool:
289-
return bool(self.subtracted_ipa_types)
288+
def has_subtracted_synth_types(self) -> bool:
289+
return bool(self.subtracted_synth_types)
290290

291291
@property
292292
def db_id(self) -> str:
@@ -304,7 +304,7 @@ def __post_init__(self):
304304

305305
@dataclass
306306
class Types:
307-
template: ClassVar = "ql_ipa_types"
307+
template: ClassVar = "ql_synth_types"
308308

309309
root: str
310310
import_prefix: str
@@ -317,7 +317,7 @@ def __post_init__(self):
317317

318318
@dataclass
319319
class ConstructorStub:
320-
template: ClassVar = "ql_ipa_constructor_stub"
320+
template: ClassVar = "ql_synth_constructor_stub"
321321

322322
cls: "Synth.FinalClass"
323323
import_prefix: str

misc/codegen/lib/schema.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class Kind(Enum):
3434
pragmas: List[str] = field(default_factory=list)
3535
doc: Optional[str] = None
3636
description: List[str] = field(default_factory=list)
37+
synth: bool = False
3738

3839
@property
3940
def is_single(self) -> bool:
@@ -74,7 +75,7 @@ def has_builtin_type(self) -> bool:
7475

7576

7677
@dataclass
77-
class IpaInfo:
78+
class SynthInfo:
7879
from_class: Optional[str] = None
7980
on_arguments: Optional[Dict[str, str]] = None
8081

@@ -87,7 +88,7 @@ class Class:
8788
properties: List[Property] = field(default_factory=list)
8889
group: str = ""
8990
pragmas: List[str] = field(default_factory=list)
90-
ipa: Optional[Union[IpaInfo, bool]] = None
91+
synth: Optional[Union[SynthInfo, bool]] = None
9192
"""^^^ filled with `True` for non-final classes with only synthesized final descendants """
9293
doc: List[str] = field(default_factory=list)
9394
default_doc_name: Optional[str] = None
@@ -104,10 +105,10 @@ def check_types(self, known: typing.Iterable[str]):
104105
_check_type(d, known)
105106
for p in self.properties:
106107
_check_type(p.type, known)
107-
if self.ipa is not None:
108-
_check_type(self.ipa.from_class, known)
109-
if self.ipa.on_arguments is not None:
110-
for t in self.ipa.on_arguments.values():
108+
if self.synth is not None:
109+
_check_type(self.synth.from_class, known)
110+
if self.synth.on_arguments is not None:
111+
for t in self.synth.on_arguments.values():
111112
_check_type(t, known)
112113

113114

misc/codegen/lib/schemadefs.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,15 @@ def __init__(self, **kwargs):
4444
self.__dict__.update(kwargs)
4545

4646

47+
class _SynthModifier(_schema.PropertyModifier, _Namespace):
48+
def modify(self, prop: _schema.Property):
49+
prop.synth = True
50+
51+
4752
qltest = _Namespace()
4853
ql = _Namespace()
4954
cpp = _Namespace()
50-
synth = _Namespace()
55+
synth = _SynthModifier()
5156

5257

5358
@_dataclass
@@ -155,7 +160,7 @@ def group(name: str = "") -> _ClassDecorator:
155160
return _annotate(group=name)
156161

157162

158-
synth.from_class = lambda ref: _annotate(ipa=_schema.IpaInfo(
163+
synth.from_class = lambda ref: _annotate(synth=_schema.SynthInfo(
159164
from_class=_schema.get_type_name(ref)))
160165
synth.on_arguments = lambda **kwargs: _annotate(
161-
ipa=_schema.IpaInfo(on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()}))
166+
synth=_schema.SynthInfo(on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()}))

0 commit comments

Comments
 (0)