Generate virtual GetTypeInfo with LCP
Reviewers: mtomic, llugovic Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1827
This commit is contained in:
parent
4fa44c3edd
commit
f91428f23f
@ -351,6 +351,104 @@ class MyClass {
|
||||
};
|
||||
```
|
||||
|
||||
#### TypeInfo
|
||||
|
||||
Defining a class or struct will also generate appropriate `utils::TypeInfo`
|
||||
instance. This instance will be stored as the `static const` member named
|
||||
`kType` and the class will also have a `GetTypeInfo` public member function,
|
||||
for getting `TypeInfo` on an instance. If a class is part of an inheritance
|
||||
hierarchy, then the `GetTypeInfo` will be `virtual`.
|
||||
|
||||
This mechanism is a portable solution for Run Time Type Information in C++.
|
||||
Take a look at the documentation of `TypeInfo` in the codebase for additional
|
||||
information on what is provided with it.
|
||||
|
||||
For example, defining a derived class:
|
||||
|
||||
```lisp
|
||||
(lcp:define-class derived-class (base-class)
|
||||
((member :int64_t)))
|
||||
```
|
||||
|
||||
Will generate:
|
||||
|
||||
```cpp
|
||||
class DerivedClass : public BaseClass {
|
||||
public:
|
||||
static const utils::TypeInfo kType;
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
private:
|
||||
int64_t member_;
|
||||
};
|
||||
```
|
||||
|
||||
The generated `TypeInfo` object does not support multiple inheritance, and LCP
|
||||
will report an error if it tries to generate such information. Additionally,
|
||||
generating type information for template classes is not supported. In such a
|
||||
case you will need to write your own description by hand. There are also cases
|
||||
where you may want to tune the generation of type information. These cases
|
||||
are described below.
|
||||
|
||||
Sometimes, you may want to avoid having a virtual or overridden call by
|
||||
treating a derived type as if it is a base type for the purposes of
|
||||
`TypeInfo`. This can be done by passing the `:type-info :base t` class option.
|
||||
NOTE: This will potentially break some `TypeInfo` related functions, like
|
||||
`IsSubtype`. Therefore, this should only be used if the base type does not
|
||||
have a correctly set `TypeInfo` interface.
|
||||
|
||||
The same example, but with the option enabled:
|
||||
|
||||
```lisp
|
||||
(lcp:define-class derived-class (base-class)
|
||||
((member :int64_t))
|
||||
(:type-info :base t))
|
||||
```
|
||||
|
||||
Will generate:
|
||||
|
||||
```cpp
|
||||
class DerivedClass : public BaseClass {
|
||||
public:
|
||||
static const utils::TypeInfo kType;
|
||||
// Has no override because we pretend this is "base".
|
||||
// Is not virtual because LCP didn't detect any class inheriting this one.
|
||||
const utils::TypeInfo &GetTypeInfo() const { return kType; }
|
||||
|
||||
private:
|
||||
int64_t member_;
|
||||
};
|
||||
```
|
||||
|
||||
Similarly to `:base t`, when you have multiple inheritance but only want to
|
||||
track the primary class as super class, you can use the
|
||||
`:ignore-other-base-classes t` option. NOTE: Similarly to the `:base t`
|
||||
option, this should only be used if you know it won't break the use cases of
|
||||
`TypeInfo` for this class hierarchy.
|
||||
|
||||
Again, the same example, but with `:ignore-other-base-classes t`:
|
||||
|
||||
```lisp
|
||||
(lcp:define-class derived-class (base-class other-base-class)
|
||||
((member :int64_t))
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
```
|
||||
|
||||
Will generate:
|
||||
|
||||
```cpp
|
||||
class DerivedClass : public BaseClass, public OtherBaseClass {
|
||||
public:
|
||||
static const utils::TypeInfo kType;
|
||||
// Overrides, because it's expected BaseClass has one. TypeInfo is set to
|
||||
// track only BaseClass as super class.
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
private:
|
||||
int64_t member_;
|
||||
};
|
||||
```
|
||||
|
||||
### Defining an RPC
|
||||
|
||||
In our codebase, we have implemented remote procedure calls. These are used
|
||||
|
@ -346,7 +346,7 @@
|
||||
;; should generate the same code that follows.
|
||||
(let ((base-save-code
|
||||
"void Save(const Base &self, slk::Builder *builder) {
|
||||
if (const auto *derived_derived = dynamic_cast<const Derived *>(&self)) {
|
||||
if (const auto *derived_derived = utils::Downcast<const Derived>(&self)) {
|
||||
return slk::Save(*derived_derived, builder);
|
||||
}
|
||||
slk::Save(Base::kType.id, builder);
|
||||
@ -372,7 +372,8 @@
|
||||
}")
|
||||
(base-load-code
|
||||
"void Load(Base *self, slk::Reader *reader) {
|
||||
// CHECK(self->GetTypeInfo() == Base::kType);
|
||||
if (self->GetTypeInfo() != Base::kType)
|
||||
throw slk::SlkDecodeException(\"Trying to load incorrect derived type!\");
|
||||
slk::Load(&self->base_member, reader);
|
||||
}")
|
||||
(derived-save-code
|
||||
@ -445,7 +446,7 @@
|
||||
((derived-member :int64_t)))))
|
||||
(is-generated (lcp.slk:save-function-definition-for-class abstract-base-class)
|
||||
"void Save(const AbstractBase &self, slk::Builder *builder) {
|
||||
if (const auto *derived_derived = dynamic_cast<const Derived *>(&self)) {
|
||||
if (const auto *derived_derived = utils::Downcast<const Derived>(&self)) {
|
||||
return slk::Save(*derived_derived, builder);
|
||||
}
|
||||
LOG(FATAL) << \"`AbstractBase` is marked as an abstract class!\";
|
||||
@ -533,7 +534,7 @@
|
||||
(declare (ignore derived-class))
|
||||
(is-generated (lcp.slk:save-function-definition-for-class base-class)
|
||||
"void Save(const Base &self, slk::Builder *builder, SaveArg extra_arg) {
|
||||
if (const auto *derived_derived = dynamic_cast<const Derived *>(&self)) {
|
||||
if (const auto *derived_derived = utils::Downcast<const Derived>(&self)) {
|
||||
return slk::Save(*derived_derived, builder, extra_arg);
|
||||
}
|
||||
LOG(FATAL) << \"`Base` is marked as an abstract class!\";
|
||||
|
@ -61,6 +61,51 @@ NIL, returns a string."
|
||||
(format stream "template <~{class ~A~^,~^ ~}>"
|
||||
(mapcar #'cpp-type-name type-params)))
|
||||
|
||||
(defun type-info-declaration-for-class (cpp-class)
|
||||
(assert (and (not (cpp-type-type-params cpp-class))
|
||||
(not (cpp-type-type-args cpp-class))))
|
||||
(with-output-to-string (s)
|
||||
(write-line "static const utils::TypeInfo kType;" s)
|
||||
(let* ((type-info-basep (type-info-opts-base (cpp-class-type-info-opts cpp-class)))
|
||||
(virtual (if (and (or type-info-basep (not (cpp-class-super-classes cpp-class)))
|
||||
(direct-subclasses-of cpp-class))
|
||||
"virtual"
|
||||
""))
|
||||
(override (if (and (not type-info-basep)
|
||||
(cpp-class-super-classes cpp-class))
|
||||
"override"
|
||||
"")))
|
||||
(format s "~A const utils::TypeInfo &GetTypeInfo() const ~A { return kType; }"
|
||||
virtual override))))
|
||||
|
||||
(defun type-info-definition-for-class (cpp-class)
|
||||
(assert (and (not (cpp-type-type-params cpp-class))
|
||||
(not (cpp-type-type-args cpp-class))))
|
||||
(with-output-to-string (s)
|
||||
(let ((super-classes (when (not (type-info-opts-base (cpp-class-type-info-opts cpp-class)))
|
||||
(cpp-class-super-classes cpp-class))))
|
||||
(when (type-info-opts-ignore-other-base-classes (cpp-class-type-info-opts cpp-class))
|
||||
(setf super-classes (list (first super-classes))))
|
||||
(when (> (length super-classes) 1)
|
||||
(error "Unable to generate TypeInfo for class '~A' due to multiple inheritance!"
|
||||
(cpp-type-base-name cpp-class)))
|
||||
(flet ((get-super-type-info (super)
|
||||
(let ((super-class (find-cpp-class super)))
|
||||
(format nil "&~A::kType"
|
||||
(if super-class
|
||||
(cpp-type-decl super-class)
|
||||
(cpp-type-name super))))))
|
||||
(format s "const utils::TypeInfo ~A::kType{0x~XULL, \"~A\", ~A};~%"
|
||||
(if *generating-cpp-impl-p*
|
||||
(cpp-type-name cpp-class)
|
||||
;; Use full type declaration if class definition
|
||||
;; isn't inside the .cpp file.
|
||||
(cpp-type-decl cpp-class))
|
||||
;; Use full type declaration for hash
|
||||
(fnv1a64-hash-string (cpp-type-decl cpp-class))
|
||||
(cpp-type-name cpp-class)
|
||||
(if super-classes (get-super-type-info (first super-classes)) "nullptr"))))))
|
||||
|
||||
(defun cpp-class-definition (cpp-class)
|
||||
"Get C++ definition of the CPP-CLASS as a string."
|
||||
(declare (type cpp-class cpp-class))
|
||||
@ -92,7 +137,7 @@ NIL, returns a string."
|
||||
(write-line " public:" s))
|
||||
(unless (cpp-type-type-params cpp-class)
|
||||
;; Skip generating TypeInfo for template classes.
|
||||
(write-line "static const utils::TypeInfo kType;" s))
|
||||
(write-line (type-info-declaration-for-class cpp-class) s))
|
||||
(format s "~%~{~A~%~}" (mapcar #'cpp-code (cpp-class-public cpp-class)))
|
||||
(format s "~{~%~A~}~%" (mapcar #'cpp-member-reader-definition reader-members))
|
||||
(format s "~{ ~%~A~}~%"
|
||||
@ -112,16 +157,7 @@ NIL, returns a string."
|
||||
;; Define the TypeInfo object. Relies on the fact that *CPP-IMPL* is
|
||||
;; processed later.
|
||||
(unless (cpp-type-type-params cpp-class)
|
||||
(let ((typeinfo-def
|
||||
(format nil "const utils::TypeInfo ~A::kType{0x~XULL, \"~a\"};~%"
|
||||
(if *generating-cpp-impl-p*
|
||||
(cpp-type-name cpp-class)
|
||||
;; Use full type declaration if class definition
|
||||
;; isn't inside the .cpp file.
|
||||
(cpp-type-decl cpp-class))
|
||||
;; Use full type declaration for hash
|
||||
(fnv1a64-hash-string (cpp-type-decl cpp-class))
|
||||
(cpp-type-name cpp-class))))
|
||||
(let ((typeinfo-def (type-info-definition-for-class cpp-class)))
|
||||
(if *generating-cpp-impl-p*
|
||||
(write-line typeinfo-def s)
|
||||
(in-impl typeinfo-def)))))))
|
||||
|
@ -222,7 +222,7 @@ CPP-CLASS. Raise `SLK-ERROR' if a derived class has template parameters."
|
||||
(extra-args (mapcar (lambda (name-and-type)
|
||||
(lcp::cpp-variable-name (first name-and-type)))
|
||||
(save-extra-args cpp-class))))
|
||||
(format s "if (const auto *~A_derived = dynamic_cast<const ~A *>(&self)) {
|
||||
(format s "if (const auto *~A_derived = utils::Downcast<const ~A>(&self)) {
|
||||
return slk::Save(*~A_derived, builder~{, ~A~}); }~%"
|
||||
derived-var derived-class derived-var extra-args))))))
|
||||
|
||||
@ -299,10 +299,9 @@ C++ constructs, mostly related to templates."
|
||||
;; We are assuming that the generated code is called only in cases when we
|
||||
;; really have this particular class instantiated and not any of the
|
||||
;; derived ones.
|
||||
;; TODO: Add the following check when we have derived classes and
|
||||
;; virtual GetTypeInfo method.
|
||||
(when (lcp::direct-subclasses-of cpp-class)
|
||||
(format s "// CHECK(self->GetTypeInfo() == ~A::kType);~%" (lcp::cpp-type-decl cpp-class)))
|
||||
(format s "if (self->GetTypeInfo() != ~A::kType)~%" (lcp::cpp-type-decl cpp-class))
|
||||
(write-line "throw slk::SlkDecodeException(\"Trying to load incorrect derived type!\");" s))
|
||||
(write-string (load-parents-recursively cpp-class) s)
|
||||
(write-string (load-members cpp-class) s)))
|
||||
|
||||
|
@ -181,6 +181,11 @@
|
||||
(ignore-other-base-classes nil :read-only t)
|
||||
(init-object nil :read-only t))
|
||||
|
||||
(defstruct type-info-opts
|
||||
"Options for generating TypeInfo of C++ class."
|
||||
(base nil :read-only t)
|
||||
(ignore-other-base-classes nil :read-only t))
|
||||
|
||||
(defclass cpp-class (cpp-type)
|
||||
((structp :type boolean :initarg :structp :initform nil
|
||||
:reader cpp-class-structp)
|
||||
@ -198,6 +203,8 @@
|
||||
:reader cpp-class-slk-opts)
|
||||
(clone-opts :type (or null clone-opts) :initarg :clone-opts :initform nil
|
||||
:reader cpp-class-clone-opts)
|
||||
(type-info-opts :type type-info-opts :initarg :type-info-opts :initform (make-type-info-opts)
|
||||
:reader cpp-class-type-info-opts)
|
||||
(inner-types :initarg :inner-types :initform nil :reader cpp-class-inner-types)
|
||||
(abstractp :initarg :abstractp :initform nil :reader cpp-class-abstractp))
|
||||
(:documentation "Meta information on a C++ class (or struct)."))
|
||||
@ -604,6 +611,8 @@ Generates C++:
|
||||
`(make-slk-opts ,@(cdr (assoc :slk serialize))))
|
||||
:clone-opts ,(when (assoc :clone options)
|
||||
`(make-clone-opts ,@(cdr (assoc :clone options))))
|
||||
:type-info-opts (make-type-info-opts ,@(when (assoc :type-info options)
|
||||
(cdr (assoc :type-info options))))
|
||||
:abstractp ,abstractp
|
||||
:namespace (reverse *cpp-namespaces*)
|
||||
;; Set inner types at the end. This works
|
||||
|
@ -406,7 +406,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class where (tree "::utils::Visitable<HierarchicalTreeVisitor>")
|
||||
((expression "Expression *" :initval "nullptr" :scope :public
|
||||
@ -439,7 +440,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class binary-operator (expression)
|
||||
((expression1 "Expression *" :initval "nullptr" :scope :public
|
||||
@ -1370,7 +1372,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; END Expressions
|
||||
@ -1402,7 +1405,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class node-atom (pattern-atom)
|
||||
((labels "std::vector<LabelIx>" :scope :public
|
||||
@ -1660,7 +1664,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class clause (tree "::utils::Visitable<HierarchicalTreeVisitor>")
|
||||
()
|
||||
@ -1681,7 +1686,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class single-query (tree "::utils::Visitable<HierarchicalTreeVisitor>")
|
||||
((clauses "std::vector<Clause *>"
|
||||
@ -1716,7 +1722,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class cypher-union (tree "::utils::Visitable<HierarchicalTreeVisitor>")
|
||||
((single-query "SingleQuery *" :initval "nullptr" :scope :public
|
||||
@ -1758,7 +1765,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class query (tree "::utils::Visitable<QueryVisitor<void>>")
|
||||
()
|
||||
@ -1779,7 +1787,8 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk :ignore-other-base-classes t)
|
||||
(:capnp :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t))
|
||||
(:clone :ignore-other-base-classes t)
|
||||
(:type-info :ignore-other-base-classes t))
|
||||
|
||||
(lcp:define-class cypher-query (query)
|
||||
((single-query "SingleQuery *" :initval "nullptr" :scope :public
|
||||
|
@ -230,6 +230,7 @@ can serve as inputs to others and thus a sequence of operations is formed.")
|
||||
(:capnp :base t
|
||||
:save-args '((helper "LogicalOperator::SaveHelper *"))
|
||||
:load-args '((helper "LogicalOperator::LoadHelper *"))))
|
||||
(:type-info :base t)
|
||||
(:clone :args '((storage "AstStorage *"))
|
||||
:base t))
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
@ -15,6 +16,9 @@ struct TypeInfo {
|
||||
uint64_t id;
|
||||
/// Pretty name of the type.
|
||||
const char *name;
|
||||
/// `TypeInfo *` for superclass of this type.
|
||||
/// Multiple inheritance is not supported.
|
||||
const TypeInfo *superclass{nullptr};
|
||||
};
|
||||
|
||||
inline bool operator==(const TypeInfo &a, const TypeInfo &b) {
|
||||
@ -36,4 +40,34 @@ inline bool operator>=(const TypeInfo &a, const TypeInfo &b) {
|
||||
return a.id >= b.id;
|
||||
}
|
||||
|
||||
/// Return true if `a` is subtype or the same type as `b`.
|
||||
inline bool IsSubtype(const TypeInfo &a, const TypeInfo &b) {
|
||||
if (a == b) return true;
|
||||
const TypeInfo *super_a = a.superclass;
|
||||
while (super_a) {
|
||||
if (*super_a == b) return true;
|
||||
super_a = super_a->superclass;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool IsSubtype(const T &a, const TypeInfo &b) {
|
||||
return IsSubtype(a.GetTypeInfo(), b);
|
||||
}
|
||||
|
||||
/// Downcast `a` to `TDerived` using static_cast.
|
||||
///
|
||||
/// If `a` is `nullptr` or `TBase` is not a subtype of `TDerived`, then a
|
||||
/// `nullptr` is returned.
|
||||
///
|
||||
/// This downcast is ill-formed if TBase is ambiguous, inaccessible, or virtual
|
||||
/// base (or a base of a virtual base) of TDerived.
|
||||
template <class TDerived, class TBase>
|
||||
TDerived *Downcast(TBase *a) {
|
||||
if (!a) return nullptr;
|
||||
if (IsSubtype(*a, TDerived::kType)) return static_cast<TDerived *>(a);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
Loading…
Reference in New Issue
Block a user