Skip to content

Commit c08e6fd

Browse files
committed
Swift codegen: add predicate properties
Properties marked with `predicate` in the schema are now accepted. * in the dbscheme, they will translate to a table with a single `id` column (and the table name will not be pluralized) * in C++ classes, they will translate to `bool` fields * in QL classes, they will translate to predicates Closes github/codeql-c-team#1016
1 parent effa9ee commit c08e6fd

File tree

17 files changed

+179
-60
lines changed

17 files changed

+179
-60
lines changed

swift/codegen/cppgen.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99

1010
def _get_type(t: str, trap_affix: str) -> str:
11+
if t is None:
12+
# this is a predicate
13+
return "bool"
1114
if t == "string":
1215
return "std::string"
1316
if t == "boolean":
@@ -20,12 +23,15 @@ def _get_type(t: str, trap_affix: str) -> str:
2023
def _get_field(cls: schema.Class, p: schema.Property, trap_affix: str) -> cpp.Field:
2124
trap_name = None
2225
if not p.is_single:
23-
trap_name = inflection.pluralize(inflection.camelize(f"{cls.name}_{p.name}"))
26+
trap_name = inflection.camelize(f"{cls.name}_{p.name}")
27+
if not p.is_predicate:
28+
trap_name = inflection.pluralize(trap_name)
2429
args = dict(
2530
name=p.name + ("_" if p.name in cpp.cpp_keywords else ""),
2631
type=_get_type(p.type, trap_affix),
2732
is_optional=p.is_optional,
2833
is_repeated=p.is_repeated,
34+
is_predicate=p.is_predicate,
2935
trap_name=trap_name,
3036
)
3137
args.update(cpp.get_field_override(p.name))

swift/codegen/dbschemegen.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ def cls_to_dbscheme(cls: schema.Class):
5757
Column(f.name, dbtype(f.type)),
5858
],
5959
)
60+
elif f.is_predicate:
61+
yield Table(
62+
keyset=KeySet(["id"]),
63+
name=inflection.underscore(f"{cls.name}_{f.name}"),
64+
columns=[
65+
Column("id", type=dbtype(cls.name)),
66+
],
67+
)
68+
6069

6170

6271
def get_declarations(data: schema.Schema):

swift/codegen/lib/cpp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Field:
3535
type: str
3636
is_optional: bool = False
3737
is_repeated: bool = False
38+
is_predicate: bool = False
3839
trap_name: str = None
3940
first: bool = False
4041

@@ -61,7 +62,7 @@ def get_streamer(self):
6162

6263
@property
6364
def is_single(self):
64-
return not (self.is_optional or self.is_repeated)
65+
return not (self.is_optional or self.is_repeated or self.is_predicate)
6566

6667

6768

swift/codegen/lib/ql.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,35 @@ class Param:
1414
@dataclass
1515
class Property:
1616
singular: str
17-
type: str
18-
tablename: str
19-
tableparams: List[Param]
17+
type: str = None
18+
tablename: str = None
19+
tableparams: List[Param] = field(default_factory=list)
2020
plural: str = None
2121
first: bool = False
2222
local_var: str = "x"
2323
is_optional: bool = False
24+
is_predicate: bool = False
2425

2526
def __post_init__(self):
26-
assert self.tableparams
27-
if self.type_is_class:
28-
self.tableparams = [x if x != "result" else self.local_var for x in self.tableparams]
29-
self.tableparams = [Param(x) for x in self.tableparams]
30-
self.tableparams[0].first = True
27+
if self.tableparams:
28+
if self.type_is_class:
29+
self.tableparams = [x if x != "result" else self.local_var for x in self.tableparams]
30+
self.tableparams = [Param(x) for x in self.tableparams]
31+
self.tableparams[0].first = True
3132

3233
@property
33-
def indefinite_article(self):
34+
def getter(self):
35+
return f"get{self.singular}" if not self.is_predicate else self.singular
36+
37+
@property
38+
def indefinite_getter(self):
3439
if self.plural:
35-
return "An" if self.singular[0] in "AEIO" else "A"
40+
article = "An" if self.singular[0] in "AEIO" else "A"
41+
return f"get{article}{self.singular}"
3642

3743
@property
3844
def type_is_class(self):
39-
return self.type[0].isupper()
45+
return bool(self.type) and self.type[0].isupper()
4046

4147
@property
4248
def is_repeated(self):

swift/codegen/lib/schema.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ class Property:
1515
is_single: ClassVar = False
1616
is_optional: ClassVar = False
1717
is_repeated: ClassVar = False
18+
is_predicate: ClassVar = False
1819

1920
name: str
20-
type: str
21+
type: str = None
2122

2223

2324
@dataclass
@@ -41,6 +42,11 @@ class RepeatedOptionalProperty(Property):
4142
is_repeated: ClassVar = True
4243

4344

45+
@dataclass
46+
class PredicateProperty(Property):
47+
is_predicate: ClassVar = True
48+
49+
4450
@dataclass
4551
class Class:
4652
name: str
@@ -58,17 +64,15 @@ class Schema:
5864

5965
def _parse_property(name, type):
6066
if type.endswith("?*"):
61-
cls = RepeatedOptionalProperty
62-
type = type[:-2]
67+
return RepeatedOptionalProperty(name, type[:-2])
6368
elif type.endswith("*"):
64-
cls = RepeatedProperty
65-
type = type[:-1]
69+
return RepeatedProperty(name, type[:-1])
6670
elif type.endswith("?"):
67-
cls = OptionalProperty
68-
type = type[:-1]
71+
return OptionalProperty(name, type[:-1])
72+
elif type == "predicate":
73+
return PredicateProperty(name)
6974
else:
70-
cls = SingleProperty
71-
return cls(name, type)
75+
return SingleProperty(name, type)
7276

7377

7478
class _DirSelector:

swift/codegen/qlgen.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ def get_ql_property(cls: schema.Class, prop: schema.Property):
3636
tableparams=["this", "result"],
3737
is_optional=True,
3838
)
39+
elif prop.is_predicate:
40+
return ql.Property(
41+
singular=inflection.camelize(prop.name, uppercase_first_letter=False),
42+
type="predicate",
43+
tablename=inflection.underscore(f"{cls.name}_{prop.name}"),
44+
tableparams=["this"],
45+
is_predicate=True,
46+
)
3947

4048

4149
def get_ql_class(cls: schema.Class):

swift/codegen/schema.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ AnyFunctionType:
5959
result: Type
6060
param_types: Type*
6161
param_labels: string*
62+
is_throwing: predicate
6263

6364
AnyGenericType:
6465
_extends: Type

swift/codegen/templates/cpp_classes.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ struct {{name}}{{#final}} : Binding<{{name}}Tag>{{#bases}}, {{ref.name}}{{/bases
3333
{{ref.name}}::emit(id, out);
3434
{{/bases}}
3535
{{#fields}}
36+
{{#is_predicate}}
37+
if ({{name}}) out << {{trap_name}}{{trap_affix}}{id} << '\n';
38+
{{/is_predicate}}
3639
{{#is_optional}}
3740
{{^is_repeated}}
3841
if ({{name}}) out << {{trap_name}}{{trap_affix}}{id, *{{name}}} << '\n';

swift/codegen/templates/ql_class.mustache

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class {{name}}Base extends {{db_id}}{{#bases}}, {{.}}{{/bases}} {
2121
{{/final}}
2222
{{#properties}}
2323

24-
{{type}} get{{singular}}({{#is_repeated}}int index{{/is_repeated}}) {
24+
{{type}} {{getter}}({{#is_repeated}}int index{{/is_repeated}}) {
2525
{{#type_is_class}}
2626
exists({{type}} {{local_var}} |
2727
{{tablename}}({{#tableparams}}{{^first}}, {{/first}}{{param}}{{/tableparams}})
@@ -34,13 +34,13 @@ class {{name}}Base extends {{db_id}}{{#bases}}, {{.}}{{/bases}} {
3434
}
3535
{{#is_repeated}}
3636

37-
{{type}} get{{indefinite_article}}{{singular}}() {
38-
result = get{{singular}}(_)
37+
{{type}} {{indefinite_getter}}() {
38+
result = {{getter}}(_)
3939
}
4040
{{^is_optional}}
4141

4242
int getNumberOf{{plural}}() {
43-
result = count(get{{indefinite_article}}{{singular}}())
43+
result = count({{indefinite_getter}}())
4444
}
4545
{{/is_optional}}
4646
{{/is_repeated}}

swift/codegen/test/test_cpp.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ def test_field_get_streamer(type, expected):
2727
assert f.get_streamer()("value") == expected
2828

2929

30-
@pytest.mark.parametrize("is_optional,is_repeated,expected", [
31-
(False, False, True),
32-
(True, False, False),
33-
(False, True, False),
34-
(True, True, False),
30+
@pytest.mark.parametrize("is_optional,is_repeated,is_predicate,expected", [
31+
(False, False, False, True),
32+
(True, False, False, False),
33+
(False, True, False, False),
34+
(True, True, False, False),
35+
(False, False, True, False),
3536
])
36-
def test_field_is_single(is_optional, is_repeated, expected):
37-
f = cpp.Field("name", "type", is_optional=is_optional, is_repeated=is_repeated)
37+
def test_field_is_single(is_optional, is_repeated, is_predicate, expected):
38+
f = cpp.Field("name", "type", is_optional=is_optional, is_repeated=is_repeated, is_predicate=is_predicate)
3839
assert f.is_single is expected
3940

4041

0 commit comments

Comments
 (0)