Files
codeql/misc/codegen/lib/schemadefs.py
2025-06-20 15:45:18 +02:00

370 lines
10 KiB
Python

from typing import (
Callable as _Callable,
Dict as _Dict,
Iterable as _Iterable,
Union as _Union,
)
from copy import deepcopy as _deepcopy
from misc.codegen.lib import schema as _schema
import inspect as _inspect
from dataclasses import dataclass as _dataclass
_set = set
@_dataclass
class _ChildModifier(_schema.PropertyModifier):
child: bool = True
def modify(self, prop: _schema.Property):
if prop.type is None or prop.type[0].islower():
raise _schema.Error("Non-class properties cannot be children")
if prop.is_unordered:
raise _schema.Error("Set properties cannot be children")
prop.is_child = self.child
def negate(self) -> _schema.PropertyModifier:
return _ChildModifier(False)
class _DocModifierMetaclass(type(_schema.PropertyModifier)):
# make ~doc same as doc(None)
def __invert__(self) -> _schema.PropertyModifier:
return _DocModifier(None)
@_dataclass
class _DocModifier(_schema.PropertyModifier, metaclass=_DocModifierMetaclass):
doc: str | None
def modify(self, prop: _schema.Property):
if self.doc and ("\n" in self.doc or self.doc[-1] == "."):
raise _schema.Error(
"No newlines or trailing dots are allowed in doc, did you intend to use desc?"
)
prop.doc = self.doc
def negate(self) -> _schema.PropertyModifier:
return _DocModifier(None)
class _DescModifierMetaclass(type(_schema.PropertyModifier)):
# make ~desc same as desc(None)
def __invert__(self) -> _schema.PropertyModifier:
return _DescModifier(None)
@_dataclass
class _DescModifier(_schema.PropertyModifier, metaclass=_DescModifierMetaclass):
description: str | None
def modify(self, prop: _schema.Property):
prop.description = _schema.split_doc(self.description)
def negate(self) -> _schema.PropertyModifier:
return _DescModifier(None)
def include(source: str):
# add to `includes` variable in calling context
_inspect.currentframe().f_back.f_locals.setdefault("includes", []).append(source)
imported = _schema.ImportedClass
@_dataclass
class _Namespace:
"""simple namespacing mechanism"""
_name: str
def add(
self, pragma: _Union["_PragmaBase", "_Parametrized"], key: str | None = None
):
self.__dict__[pragma.pragma] = pragma
pragma.pragma = key or f"{self._name}_{pragma.pragma}"
@_dataclass
class _SynthModifier(_schema.PropertyModifier, _Namespace):
synth: bool = True
def modify(self, prop: _schema.Property):
prop.synth = self.synth
def negate(self) -> _schema.PropertyModifier:
return _SynthModifier(self._name, False)
qltest = _Namespace("qltest")
ql = _Namespace("ql")
cpp = _Namespace("cpp")
rust = _Namespace("rust")
synth = _SynthModifier("synth")
@_dataclass
class _PragmaBase:
pragma: str
value: object = None
def _apply(self, pragmas: _Dict[str, object]) -> None:
pragmas[self.pragma] = self.value
@_dataclass
class _ClassPragma(_PragmaBase):
"""A class pragma.
For schema classes it acts as a python decorator with `@`.
"""
inherited: bool = False
def __call__(self, cls: type) -> type:
"""use this pragma as a decorator on classes"""
if self.inherited:
setattr(
cls, f"{_schema.inheritable_pragma_prefix}{self.pragma}", self.value
)
else:
# not using hasattr as we don't want to land on inherited pragmas
if "_pragmas" not in cls.__dict__:
cls._pragmas = {}
self._apply(cls._pragmas)
return cls
@_dataclass
class _PropertyPragma(_PragmaBase, _schema.PropertyModifier):
"""A property pragma.
It functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
"""
remove: bool = False
def modify(self, prop: _schema.Property):
self._apply(prop.pragmas)
def negate(self) -> _schema.PropertyModifier:
return _PropertyPragma(self.pragma, remove=not self.remove)
def _apply(self, pragmas: _Dict[str, object]) -> None:
if self.remove:
pragmas.pop(self.pragma, None)
else:
super()._apply(pragmas)
@_dataclass
class _Pragma(_ClassPragma, _PropertyPragma):
"""A class or property pragma.
For properties, it functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
For schema classes it acts as a python decorator with `@`.
"""
class _Parametrized[P, **Q, T]:
"""A parametrized pragma.
Needs to be applied to a parameter to give a pragma.
"""
def __init__(self, pragma_instance: P, factory: _Callable[Q, T]):
self.pragma_instance = pragma_instance
self.factory = factory
self.__signature__ = _inspect.signature(self.factory).replace(
return_annotation=type(self.pragma_instance)
)
@property
def pragma(self):
return self.pragma_instance.pragma
@pragma.setter
def pragma(self, value):
self.pragma_instance.pragma = value
def __invert__(self) -> "_Parametrized[P, Q, T]":
return _Parametrized(~self.pragma_instance, factory=self.factory)
def __call__(self, *args: Q.args, **kwargs: Q.kwargs) -> T:
ret = _deepcopy(self.pragma_instance)
ret.value = self.factory(*args, **kwargs)
return ret
class _Optionalizer(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind != K.SINGLE:
raise _schema.Error(
"optional should only be applied to simple property types"
)
prop.kind = K.OPTIONAL
class _Listifier(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind == K.SINGLE:
prop.kind = K.REPEATED
elif prop.kind == K.OPTIONAL:
prop.kind = K.REPEATED_OPTIONAL
else:
raise _schema.Error(
"list should only be applied to simple or optional property types"
)
class _Setifier(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind != K.SINGLE:
raise _schema.Error("set should only be applied to simple property types")
prop.kind = K.REPEATED_UNORDERED
class _TypeModifier:
"""Modifies types using get item notation"""
def __init__(self, modifier: _schema.PropertyModifier):
self.modifier = modifier
def __getitem__(self, item):
return item | self.modifier
_ClassDecorator = _Callable[[type], type]
boolean = "boolean"
int = "int"
string = "string"
predicate = _schema.predicate_marker
optional = _TypeModifier(_Optionalizer())
list = _TypeModifier(_Listifier())
set = _TypeModifier(_Setifier())
child = _ChildModifier()
doc = _DocModifier
desc = _DescModifier
use_for_null = _ClassPragma("null")
qltest.add(_ClassPragma("skip"))
qltest.add(_ClassPragma("collapse_hierarchy"))
qltest.add(_ClassPragma("uncollapse_hierarchy"))
qltest.add(
_Parametrized(
_ClassPragma("test_with", inherited=True), factory=_schema.get_type_name
)
)
ql.add(_Parametrized(_ClassPragma("default_doc_name"), factory=lambda doc: doc))
ql.add(_ClassPragma("hideable", inherited=True))
ql.add(_Pragma("internal"))
ql.add(_Parametrized(_Pragma("name"), factory=lambda name: name))
ql.add(_Parametrized(_PropertyPragma("db_table_name"), factory=lambda name: name))
cpp.add(_Pragma("skip"))
rust.add(_PropertyPragma("detach"))
rust.add(_Pragma("skip_doc_test"))
rust.add(
_Parametrized(
_ClassPragma("doc_test_signature"), factory=lambda signature: signature
)
)
group = _Parametrized(
_ClassPragma("group", inherited=True), factory=lambda group: group
)
synth.add(
_Parametrized(
_ClassPragma("from_class"),
factory=lambda ref: _schema.SynthInfo(from_class=_schema.get_type_name(ref)),
),
key="synth",
)
synth.add(
_Parametrized(
_ClassPragma("on_arguments"),
factory=lambda **kwargs: _schema.SynthInfo(
on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()}
),
),
key="synth",
)
@_dataclass(frozen=True)
class _PropertyModifierList(_schema.PropertyModifier):
_mods: tuple[_schema.PropertyModifier, ...]
def __or__(self, other: _schema.PropertyModifier):
return _PropertyModifierList(self._mods + (other,))
def modify(self, prop: _schema.Property):
for m in self._mods:
m.modify(prop)
_ = _PropertyModifierList(())
drop = object()
def annotate(
annotated_cls: type,
add_bases: _Iterable[type] | None = None,
replace_bases: _Dict[type, type | None] | None = None,
cfg: bool = False,
) -> _Callable[[type], _PropertyModifierList]:
"""
Add or modify schema annotations after a class has been defined previously.
The name of the class used for annotation must be `_`.
`replace_bases` can be used to replace bases on the annotated class. Mapping to
`None` will remove that base class.
"""
def decorator(cls: type) -> _PropertyModifierList:
if cls.__name__ != "_":
raise _schema.Error("Annotation classes must be named _")
if cls.__doc__ is not None:
annotated_cls.__doc__ = cls.__doc__
for p, v in cls.__dict__.get("_pragmas", {}).items():
_ClassPragma(p, value=v)(annotated_cls)
if replace_bases:
annotated_cls.__bases__ = tuple(
b
for b in (replace_bases.get(b, b) for b in annotated_cls.__bases__)
if b is not None
)
if add_bases:
annotated_cls.__bases__ += tuple(add_bases)
annotated_cls.__cfg__ = cfg
for a in dir(cls):
if a.startswith(_schema.inheritable_pragma_prefix):
setattr(annotated_cls, a, getattr(cls, a))
for p, a in cls.__annotations__.items():
if a is drop:
del annotated_cls.__annotations__[p]
elif p in annotated_cls.__annotations__:
annotated_cls.__annotations__[p] |= a
elif isinstance(a, (_PropertyModifierList, _PropertyModifierList)):
raise _schema.Error(
f"annotated property {p} not present in annotated class "
f"{annotated_cls.__name__}"
)
else:
annotated_cls.__annotations__[p] = a
return _
return decorator