Swift: tag -> pragma in codegen

For the use the former tags are meant for, pragma is a more
meaningful name. It now also accepts both strings and lists of strings.
This commit is contained in:
Paolo Tranquilli
2022-06-23 10:39:21 +02:00
parent 3248f7b423
commit 8d4637ddfd
5 changed files with 107 additions and 44 deletions

View File

@@ -19,7 +19,7 @@ class FormatError(Exception):
def get_ql_property(cls: schema.Class, prop: schema.Property):
common_args = dict(
type=prop.type if not prop.is_predicate else "predicate",
skip_qltest="no_qltest" in prop.tags,
skip_qltest="skip_qltest" in prop.pragmas,
is_child=prop.is_child,
is_optional=prop.is_optional,
is_predicate=prop.is_predicate,
@@ -64,7 +64,7 @@ def get_ql_class(cls: schema.Class):
final=not cls.derived,
properties=[get_ql_property(cls, p) for p in cls.properties],
dir=cls.dir,
skip_qltest="no_qltest" in cls.tags,
skip_qltest="skip_qltest" in cls.pragmas,
)

View File

@@ -2,6 +2,7 @@
import pathlib
import re
import typing
from dataclasses import dataclass, field
from typing import List, Set, Union, Dict, ClassVar
@@ -11,7 +12,7 @@ import yaml
class Error(Exception):
def __str__(self):
return f"schema.Error{args}"
return self.args[0]
root_class_name = "Element"
@@ -27,7 +28,7 @@ class Property:
name: str
type: str = None
is_child: bool = False
tags: List[str] = field(default_factory=list)
pragmas: List[str] = field(default_factory=list)
@dataclass
@@ -63,7 +64,7 @@ class Class:
derived: Set[str] = field(default_factory=set)
properties: List[Property] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
tags: List[str] = field(default_factory=list)
pragmas: List[str] = field(default_factory=list)
@dataclass
@@ -72,24 +73,38 @@ class Schema:
includes: Set[str] = field(default_factory=set)
def _parse_property(name: str, type: Union[str, Dict[str, str]], is_child: bool = False):
if isinstance(type, dict):
tags = type.get("_tags", [])
type = type["type"]
_StrOrList = Union[str, List[str]]
def _auto_list(data: _StrOrList) -> List[str]:
if isinstance(data, list):
return data
return [data]
def _parse_property(name: str, data: Union[str, Dict[str, _StrOrList]], is_child: bool = False):
if isinstance(data, dict):
if "type" not in data:
raise Error(f"property {name} has no type")
pragmas = _auto_list(data.pop("_pragma", []))
type = data.pop("type")
if data:
raise Error(f"unknown metadata {', '.join(data)} in property {name}")
else:
tags = []
pragmas = []
type = data
if is_child and type[0].islower():
raise Error(f"children must have class type, got {type} for {name}")
if type.endswith("?*"):
return RepeatedOptionalProperty(name, type[:-2], is_child=is_child, tags=tags)
return RepeatedOptionalProperty(name, type[:-2], is_child=is_child, pragmas=pragmas)
elif type.endswith("*"):
return RepeatedProperty(name, type[:-1], is_child=is_child, tags=tags)
return RepeatedProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
elif type.endswith("?"):
return OptionalProperty(name, type[:-1], is_child=is_child, tags=tags)
return OptionalProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
elif type == "predicate":
return PredicateProperty(name, tags=tags)
return PredicateProperty(name, pragmas=pragmas)
else:
return SingleProperty(name, type, is_child=is_child, tags=tags)
return SingleProperty(name, type, is_child=is_child, pragmas=pragmas)
class _DirSelector:
@@ -120,8 +135,7 @@ def load(path):
if not k.startswith("_"):
cls.properties.append(_parse_property(k, v))
elif k == "_extends":
if not isinstance(v, list):
v = [v]
v = _auto_list(v)
for base in v:
cls.bases.add(base)
classes[base].derived.add(name)
@@ -129,8 +143,8 @@ def load(path):
cls.dir = pathlib.Path(v)
elif k == "_children":
cls.properties.extend(_parse_property(kk, vv, is_child=True) for kk, vv in v.items())
elif k == "_tags":
cls.tags = v
elif k == "_pragma":
cls.pragmas = _auto_list(v)
else:
raise Error(f"unknown metadata {k} for class {name}")
if not cls.bases and cls.name != root_class_name:

View File

@@ -14,14 +14,14 @@ _directories:
Element:
is_unknown: predicate
_tags: [ no_qltest ]
_pragma: skip_qltest
File:
name: string
Locatable:
location: Location?
_tags: [ no_qltest ]
_pragma: skip_qltest
Location:
file: File
@@ -29,7 +29,7 @@ Location:
start_column: int
end_line: int
end_column: int
_tags: [ no_qltest ]
_pragma: skip_qltest
Type:
diagnostics_name: string
@@ -385,7 +385,7 @@ EnumIsCaseExpr:
ErrorExpr:
_extends: Expr
_tags: [ no_qltest ] # unexpected emission
_pragma: skip_qltest # unexpected emission
ExplicitCastExpr:
_extends: Expr
@@ -460,7 +460,7 @@ ObjCSelectorExpr:
_children:
sub_expr: Expr
method: AbstractFunctionDecl
_tags: [ no_qltest ] # to be tested in integration tests
_pragma: skip_qltest # to be tested in integration tests
OneWayExpr:
_extends: Expr
@@ -502,7 +502,7 @@ SequenceExpr:
_extends: Expr
_children:
elements: Expr*
_tags: [ no_qltest ] # we should really never extract these, as these should be resolved to trees of operations
_pragma: skip_qltest # we should really never extract these, as these should be resolved to trees of operations
SuperRefExpr:
_extends: Expr
@@ -534,7 +534,7 @@ TypeExpr:
UnresolvedDeclRefExpr:
_extends: Expr
name: string?
_tags: [ no_qltest ] # we should really never extract these
_pragma: skip_qltest # we should really never extract these
UnresolvedDotExpr:
_extends: Expr
@@ -545,15 +545,15 @@ UnresolvedDotExpr:
UnresolvedMemberExpr:
_extends: Expr
name: string
_tags: [ no_qltest ] # we should really never extract these
_pragma: skip_qltest # we should really never extract these
UnresolvedPatternExpr:
_extends: Expr
_tags: [ no_qltest ] # we should really never extract these
_pragma: skip_qltest # we should really never extract these
UnresolvedSpecializeExpr:
_extends: Expr
_tags: [ no_qltest ] # we should really never extract these
_pragma: skip_qltest # we should really never extract these
VarargExpansionExpr:
_extends: Expr
@@ -821,11 +821,11 @@ ArrayToPointerExpr:
BridgeFromObjCExpr:
_extends: ImplicitConversionExpr
_tags: [ no_qltest ] # to be tested in integration tests
_pragma: skip_qltest # to be tested in integration tests
BridgeToObjCExpr:
_extends: ImplicitConversionExpr
_tags: [ no_qltest ] # to be tested in integration tests
_pragma: skip_qltest # to be tested in integration tests
ClassMetatypeToObjectExpr:
_extends: ImplicitConversionExpr
@@ -835,7 +835,7 @@ CollectionUpcastConversionExpr:
ConditionalBridgeFromObjCExpr:
_extends: ImplicitConversionExpr
_tags: [ no_qltest ] # to be tested in integration tests
_pragma: skip_qltest # to be tested in integration tests
CovariantFunctionConversionExpr:
_extends: ImplicitConversionExpr

View File

@@ -458,13 +458,13 @@ def test_test_properties_skipped(opts, generate_tests):
write(opts.ql_test_output / "Derived" / "test.swift")
assert generate_tests([
schema.Class("Base", derived={"Derived"}, properties=[
schema.SingleProperty("x", "string", tags=["no_qltest", "foo"]),
schema.RepeatedProperty("y", "int", tags=["bar", "no_qltest"]),
schema.SingleProperty("x", "string", pragmas=["skip_qltest", "foo"]),
schema.RepeatedProperty("y", "int", pragmas=["bar", "skip_qltest"]),
]),
schema.Class("Derived", bases={"Base"}, properties=[
schema.PredicateProperty("a", tags=["no_qltest"]),
schema.PredicateProperty("a", pragmas=["skip_qltest"]),
schema.OptionalProperty(
"b", "int", tags=["bar", "no_qltest", "baz"]),
"b", "int", pragmas=["bar", "skip_qltest", "baz"]),
]),
]) == {
"Derived/Derived.ql": ql.ClassTester(class_name="Derived"),
@@ -474,7 +474,7 @@ def test_test_properties_skipped(opts, generate_tests):
def test_test_base_class_skipped(opts, generate_tests):
write(opts.ql_test_output / "Derived" / "test.swift")
assert generate_tests([
schema.Class("Base", derived={"Derived"}, tags=["no_qltest", "foo"], properties=[
schema.Class("Base", derived={"Derived"}, pragmas=["skip_qltest", "foo"], properties=[
schema.SingleProperty("x", "string"),
schema.RepeatedProperty("y", "int"),
]),
@@ -488,7 +488,7 @@ def test_test_final_class_skipped(opts, generate_tests):
write(opts.ql_test_output / "Derived" / "test.swift")
assert generate_tests([
schema.Class("Base", derived={"Derived"}),
schema.Class("Derived", bases={"Base"}, tags=["no_qltest", "foo"], properties=[
schema.Class("Derived", bases={"Base"}, pragmas=["skip_qltest", "foo"], properties=[
schema.SingleProperty("x", "string"),
schema.RepeatedProperty("y", "int"),
]),

View File

@@ -217,32 +217,81 @@ A:
]
def test_property_with_explicit_type_and_tags(load):
def test_property_with_explicit_type_and_pragmas(load):
ret = load("""
A:
x:
type: string*
_tags: [foo, bar]
_pragma: [foo, bar]
""")
assert ret.classes == [
schema.Class(root_name, derived={'A'}),
schema.Class('A', bases={root_name}, properties=[
schema.RepeatedProperty('x', 'string', tags=["foo", "bar"]),
schema.RepeatedProperty('x', 'string', pragmas=["foo", "bar"]),
]),
]
def test_class_with_tags(load):
def test_property_with_explicit_type_and_one_pragma(load):
ret = load("""
A:
x:
type: string*
_pragma: foo
""")
assert ret.classes == [
schema.Class(root_name, derived={'A'}),
schema.Class('A', bases={root_name}, properties=[
schema.RepeatedProperty('x', 'string', pragmas=["foo"]),
]),
]
def test_property_with_explicit_type_and_unknown_metadata(load):
with pytest.raises(schema.Error):
load("""
A:
x:
type: string*
_what_is_this: [foo, bar]
""")
def test_property_with_dict_without_explicit_type(load):
with pytest.raises(schema.Error):
load("""
A:
x:
typo: string*
""")
def test_class_with_pragmas(load):
ret = load("""
A:
x: string*
_tags: [foo, bar]
_pragma: [foo, bar]
""")
assert ret.classes == [
schema.Class(root_name, derived={'A'}),
schema.Class('A', bases={root_name}, properties=[
schema.RepeatedProperty('x', 'string'),
], tags=["foo", "bar"]),
], pragmas=["foo", "bar"]),
]
def test_class_with_one_pragma(load):
ret = load("""
A:
x: string*
_pragma: foo
""")
assert ret.classes == [
schema.Class(root_name, derived={'A'}),
schema.Class('A', bases={root_name}, properties=[
schema.RepeatedProperty('x', 'string'),
], pragmas=["foo"]),
]