memgraph/src/lisp/clone.lisp
Lovro Lugovic 017dec8c0e LCP: Refactor the type representation
Summary:
# Summary

## Concepts and terminology

A **namestring for <object>** is a Lisp string that names the C++ language element
`<object>` (such as a namespace, a variable, a class, etc.). Therefore we have a
"namestring for a namespace", a "namestring for a variable", etc.

A **designator for a namestring for <object>** is a Lisp object that denotes a
"namestring for <object>". These are symbols and strings themselves.

A **typestring** is a Lisp string that represents a C++ type, i.e. its
declaration.

A **typestring designator** is a Lisp object that denotes a typestring.

A typestring (and the corresponding C++ type) is said to be **supported** if it
can be parsed using `parse-cpp-type-declaration`. This concept is important and
should form the base of our design because we can't really hope to ever support
all of C++'s type declarations.

A typestring (and the corresponding C++ type) that is not supported is
**unsupported**.

A **processed typestring** is a typestring that is either fully qualified or
unqualified.

A C++ type is said to be **known** if LCP knows extra information about the type,
rather than just what kind of type it is, in which namespace it lives, etc. For
now, the only known types are those that are defined within LCP itself using
`define-class` & co.

A C++ type is **unknown** if it is not known.

**Typestring resolution** is the process of resolving a (processed) typestring
into an instance of `cpp-type` or `unsupported-cpp-type`.

**Resolving accessors** are accessors which perform typestring resolution.

## Changes

Explicitly introduce the concept of supported and known types. `cpp-type` models
supported types while `unsupported-cpp-type` models unsupported types.
Subclasses of `cpp-type` model known types. `general-cpp-type` is either a
`cpp-type` or an `unsupported-cpp-type`.

Add various type queries.

Fix `define-rpc`'s `:initarg` (remove it from the `cpp-member` struct).

Introduce namestrings and their designators in `names.lisp`.

Introduce typestrings and their designators.

Introduce **processed typestrings**. Our DSL's macros (`define-class` & co.)
convert all of the given typestrings into processed typestrings because we don't
attempt to support partially qualified names and relative name lookup yet. A
warning is signalled when a partially qualified name is treated as a fully
qualified name.

The slots of `cpp-type`, `cpp-class`, `cpp-member` and `cpp-capnp-opts` now
store processed typestrings which are lazily resolved into their corresponding
C++ types.

The only thing that instances of `unsupported-cpp-type` are good for is getting
the typestring that was used to construct them. Most of LCP's functions only
work with known C++ types, i.e. `cpp-type` instances. The only function so far
that works for both of them is `cpp-type-decl`.

Since "unsupportedness" is now explicitly part of LCP, client code is expected
to manually check whether a returned type is unsupported or not (or face
receiving an error otherwise), unless a function is documented to return only
`cpp-type` instances.

A similar thing goes for "knowness". Client code is expected to manually check
whether a returned type is known or not, unless a function is documented to
return only (instances of `cpp-type` subclasses) known types.

## TODO

Resolution still has to be done for the following slots of the
following structures:

-   `slk-opts`
    -   `save-args`
    -   `load-args`

-   `clone-opts`
    -   `args`
    -   `return-type`

Reviewers: teon.banek, mtomic

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1962
2019-05-10 16:18:31 +02:00

261 lines
12 KiB
Common Lisp

(in-package #:lcp.clone)
(define-condition clone-error (error)
((message :type string :initarg :message :reader clone-error-message)
(format-args :type list :initform nil :initarg :format-args :reader clone-error-format-args))
(:report (lambda (condition stream)
(apply #'format stream
(clone-error-message condition)
(clone-error-format-args condition)))))
(defun clone-error (message &rest format-args)
(error 'clone-error :message message :format-args format-args))
(defun cloning-parent (cpp-class)
(check-type cpp-class lcp::cpp-type)
(let ((supers (lcp::cpp-class-super-classes cpp-class))
(opts (lcp::cpp-class-clone-opts cpp-class)))
(unless opts
(clone-error "Class ~A isn't cloneable" (lcp::cpp-type-name cpp-class)))
(cond
((lcp::clone-opts-base opts)
nil)
((lcp::clone-opts-ignore-other-base-classes opts)
(car supers))
(t
(when (> (length supers) 1)
(clone-error "Cloning doesn't support multiple inheritance (class '~A', parents: '~A')"
(lcp::cpp-type-name cpp-class) supers))
(car supers)))))
(defun cloning-root (cpp-class)
(check-type cpp-class lcp::cpp-type)
(let ((parent-class (cloning-parent cpp-class)))
(if parent-class
(cloning-root parent-class)
cpp-class)))
(defun members-for-cloning (cpp-class)
(check-type cpp-class lcp::cpp-type)
(alexandria:flatten
(reverse
(loop :for current := cpp-class :then (cloning-parent current)
:while current
:collect (remove-if-not #'lcp::cpp-member-clone
(lcp::cpp-class-members current))))))
(defun copy-object (source-name dest-name)
(format nil "~A = ~A;" dest-name source-name))
(defun clone-by-copy-p (object-type)
(check-type object-type lcp::cpp-type)
(cond
((string= "vector" (lcp::cpp-type-name object-type))
(clone-by-copy-p (car (lcp::cpp-type-type-args object-type))))
((string= "optional" (lcp::cpp-type-name object-type))
(clone-by-copy-p (car (lcp::cpp-type-type-args object-type))))
((string= "unordered_map" (lcp::cpp-type-name object-type))
(and (clone-by-copy-p (first (lcp::cpp-type-type-args object-type)))
(clone-by-copy-p (second (lcp::cpp-type-type-args object-type)))))
((string= "pair" (lcp::cpp-type-name object-type))
(and (clone-by-copy-p (first (lcp::cpp-type-type-args object-type)))
(clone-by-copy-p (second (lcp::cpp-type-type-args object-type)))))
((lcp::cpp-type-type-args object-type) nil)
((or (lcp::cpp-enum-p object-type)
(lcp::cpp-type-primitive-p object-type)
(string= "string" (lcp::cpp-type-name object-type))
;; TODO: We might want to forbid implicit copying of unknown types once
;; there's a way to globally mark type as trivially copyable. Now it is
;; too annoying to add (:clone :copy) option everywhere.
(not (lcp::cpp-class-p object-type)))
t)
(t
;; We know now that we're dealing with a C++ class defined in LCP. A class
;; is cloneable by copy only if it doesn't have `Clone` function defined,
;; all of its members are cloneable by copy and it is not a member of a
;; class hierarchy.
(assert (and (lcp::cpp-type-known-p object-type)
(lcp::cpp-type-class-p object-type)))
(and (not (lcp::cpp-class-clone-opts object-type))
(not (lcp::cpp-class-direct-subclasses object-type))
(not (lcp::cpp-class-super-classes object-type))
(every (lambda (member)
(or (eq (lcp::cpp-member-clone member) :copy)
(clone-by-copy-p (lcp::cpp-member-type member))))
(lcp::cpp-class-members object-type))))))
(defun clone-object (object-type source-name dest-name &key args)
(check-type object-type lcp::cpp-type)
(let ((arg-list (format nil "~{~A~^, ~}"
(mapcar (lambda (name-and-type)
(lcp::cpp-name-for-variable (first name-and-type)))
args))))
(cond
((clone-by-copy-p object-type)
(copy-object source-name dest-name))
((lcp::cpp-type-pointer-p object-type)
(format nil "~A = ~A ? ~A->Clone(~A) : nullptr;"
dest-name source-name source-name arg-list))
((string= "optional" (lcp::cpp-type-name object-type))
(let ((value-type (car (lcp::cpp-type-type-args object-type))))
(clone-optional value-type source-name dest-name :args args)))
((string= "vector" (lcp::cpp-type-name object-type))
(let ((elem-type (car (lcp::cpp-type-type-args object-type))))
(clone-vector elem-type source-name dest-name :args args)))
((string= "unordered_map" (lcp::cpp-type-name object-type))
(let ((key-type (first (lcp::cpp-type-type-args object-type)))
(value-type (second (lcp::cpp-type-type-args object-type))))
(clone-map key-type value-type source-name dest-name :args args)))
((string= "pair" (lcp::cpp-type-name object-type))
(let ((first-type (first (lcp::cpp-type-type-args object-type)))
(second-type (second (lcp::cpp-type-type-args object-type))))
(clone-pair first-type second-type source-name dest-name :args args)))
((and (lcp::cpp-class-p object-type)
(lcp::cpp-class-clone-opts object-type))
(format nil "~A = ~A.Clone(~A);" dest-name source-name arg-list))
(t (clone-error "Don't know how to clone object of type ~A"
(lcp::cpp-type-decl object-type))))))
(defun clone-vector (elem-type source-name dest-name &key args)
(lcp::with-vars ((loop-counter "i"))
(format nil
"~A.resize(~A.size());
for (auto ~A = 0; ~A < ~A.size(); ++~A) { ~A }"
dest-name source-name
loop-counter loop-counter source-name loop-counter
(clone-object elem-type
(format nil "~A[~A]" source-name loop-counter)
(format nil "~A[~A]" dest-name loop-counter)
:args args))))
(defun clone-map (key-type value-type source-name dest-name &key args)
(lcp::with-vars ((loop-var "kv") (entry-var "entry"))
(let ((entry-type (lcp::make-cpp-type
"pair"
:namespace '("std")
:type-args (list key-type value-type))))
(format nil
"for (const auto &~A : ~A) {
~A ~A;
~A
~A.emplace(std::move(~A));
}"
loop-var source-name
(lcp::cpp-type-decl entry-type) entry-var
(clone-object entry-type loop-var entry-var :args args)
dest-name entry-var))))
(defun clone-optional (value-type source-name dest-name &key args)
(lcp::with-vars ((value-var "value"))
(format nil
"if (~A) {
~A ~A;
~A
~A.emplace(std::move(~A));
} else {
~A = std::nullopt;
}"
source-name
(lcp::cpp-type-decl value-type) value-var
(clone-object value-type
(format nil "(*~A)" source-name)
value-var
:args args)
dest-name value-var
dest-name)))
(defun clone-pair (first-type second-type source-name dest-name &key args)
(lcp::with-vars ((first-var "first") (second-var "second"))
(with-output-to-string (cpp-out)
(lcp::with-cpp-block-output (cpp-out)
(format cpp-out
"~A ~A;
~A
~A ~A;
~A
~A = std::make_pair(std::move(~A), std::move(~A));"
(lcp::cpp-type-decl first-type) first-var
(clone-object first-type
(format nil "~A.first" source-name)
first-var
:args args)
(lcp::cpp-type-decl second-type) second-var
(clone-object second-type
(format nil "~A.second" source-name)
second-var
:args args)
dest-name first-var second-var))
cpp-out)))
(defun clone-function-definition-for-class (cpp-class)
(check-type cpp-class lcp::cpp-class)
(when (lcp::cpp-type-class-template-p cpp-class)
(clone-error "Don't know how to clone class template '~A'"
(lcp::cpp-type-name cpp-class)))
(let* ((cloning-root (cloning-root cpp-class))
(root-opts (lcp::cpp-class-clone-opts cloning-root))
(inheritancep (or (lcp::cpp-class-direct-subclasses cpp-class)
(cloning-parent cpp-class)))
(return-type
(cond
((lcp::clone-opts-return-type root-opts)
(lcp::cpp-code
(funcall (lcp::clone-opts-return-type root-opts)
(lcp::cpp-type-name cpp-class))))
(inheritancep
(format nil "std::unique_ptr<~A>"
(lcp::cpp-type-name (cloning-root cpp-class))))
(t
(lcp::cpp-type-name cpp-class))))
(declaration
(lcp::cpp-method-declaration
cpp-class "Clone"
:args (lcp::clone-opts-args root-opts)
:returns return-type
:virtual (and inheritancep
(eq cpp-class cloning-root))
:inline t
:const t
:override (and inheritancep
(not (eq cpp-class cloning-root)))
:delete (lcp::cpp-class-abstractp cpp-class))))
(if (lcp::cpp-class-abstractp cpp-class)
(return-from clone-function-definition-for-class
(format nil "~A;" declaration)))
(with-output-to-string (cpp-out)
(lcp::with-cpp-block-output (cpp-out :name declaration :semicolonp nil)
(let (object-access)
(cond
((lcp::clone-opts-init-object root-opts)
(setf object-access "object->")
(write-line
(lcp::cpp-code
(funcall (lcp::clone-opts-init-object root-opts)
"object" (lcp::cpp-type-name cpp-class)))
cpp-out))
(inheritancep
(setf object-access "object->")
(format cpp-out "~&auto object = std::make_unique<~A>();"
(lcp::cpp-type-name cpp-class)))
(t
(setf object-access "object.")
(format cpp-out "~&~A object;"
(lcp::cpp-type-name cpp-class))))
(dolist (member (members-for-cloning cpp-class))
(let* ((source (lcp::cpp-member-name member))
(dest (format nil "~A~A" object-access source)))
(cond
((eq (lcp::cpp-member-clone member) :copy)
(format cpp-out "~&~A" (copy-object source dest)))
((functionp (lcp::cpp-member-clone member))
(format cpp-out "~&~A"
(lcp::cpp-code (funcall (lcp::cpp-member-clone member)
source dest))))
(t
(format cpp-out "~&~A"
(clone-object
(lcp::cpp-member-type member)
source dest
:args (lcp::clone-opts-args root-opts)))))))
(format cpp-out "~&return object;"))))))