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:
Teon Banek 2019-01-25 10:15:15 +01:00
parent 4fa44c3edd
commit f91428f23f
8 changed files with 215 additions and 28 deletions

View File

@ -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

View File

@ -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!\";

View File

@ -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)))))))

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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