From cc45b21750f9fec333e9ed8fc0d2bff049aa1769 Mon Sep 17 00:00:00 2001 From: "Scott L. Burson" Date: Tue, 30 Jun 2026 15:20:17 -0700 Subject: [PATCH 1/3] Handle explicit/deleted copy constructor and assignment --- src/gen/common/generator/function.lisp | 4 +- src/gen/iffi/cxx/generator/class.lisp | 14 +--- src/resect/resect.lisp | 15 ++++ src/spec/entity.lisp | 101 ++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/gen/common/generator/function.lisp b/src/gen/common/generator/function.lisp index ed5e98e..79482f9 100644 --- a/src/gen/common/generator/function.lisp +++ b/src/gen/common/generator/function.lisp @@ -96,8 +96,8 @@ :location param-location) :name (c-name->lisp param-name :parameter) :value (cond - ((and (typep adapted 'claw.spec:foreign-reference) - (claw.spec:foreign-reference-rvalue-p adapted)) + ((and adapted + (not (claw.spec:foreign-type-copy-constructible-p adapted))) (format nil "std::move(*~A)" param-name)) (adapted (format nil "*~A" param-name)) (t param-name)) diff --git a/src/gen/iffi/cxx/generator/class.lisp b/src/gen/iffi/cxx/generator/class.lisp index 9f567fa..f6b83dd 100644 --- a/src/gen/iffi/cxx/generator/class.lisp +++ b/src/gen/iffi/cxx/generator/class.lisp @@ -42,16 +42,10 @@ (defun adapt-setter (record field) (let* ((field-name (claw.spec:foreign-entity-name field)) - (original-type (claw.spec:foreign-enveloped-entity field)) - (unaliased (claw.spec:unalias-foreign-entity original-type))) - (multiple-value-bind (field-type adapted-p) - (adapt-type original-type) - (unless (or (typep unaliased 'claw.spec:foreign-array) - (typep (if (or (typep unaliased 'claw.spec:foreign-pointer) - (typep unaliased 'claw.spec:foreign-reference)) - (claw.spec:foreign-enveloped-entity unaliased) - unaliased) - 'claw.spec:foreign-const-qualifier)) + (original-type (claw.spec:foreign-enveloped-entity field))) + (when (claw.spec:foreign-type-assignable-p original-type) + (multiple-value-bind (field-type adapted-p) + (adapt-type original-type) (make-instance 'adapted-function :name (format nil "set_~A_~A" (mangle-entity-name record) diff --git a/src/resect/resect.lisp b/src/resect/resect.lisp index c2152d2..736ae0d 100644 --- a/src/resect/resect.lisp +++ b/src/resect/resect.lisp @@ -502,6 +502,16 @@ (with-slots (deps) this (push dependent deps)))) +(defmethod foreign-type-assignable-p ((record resect-record)) + (and (call-next-method) + ;; For templates like `std::optional', `std::vector', etc., we don't see the class + ;; declaration, but it's a pretty safe assumption that an instantiation includes at least + ;; one field of the argument type. + (every (lambda (arg) + (or (not (typep (foreign-entity-parameter arg) 'foreign-entity-type-parameter)) + (foreign-type-assignable-p (foreign-entity-value arg)))) + (arguments-of record)))) + (defclass resect-struct (resect-record foreign-struct) ()) (defclass resect-union (resect-record foreign-union) ()) @@ -798,6 +808,11 @@ :bit-size (%resect:type-size decl-type) :bit-alignment (%resect:type-alignment decl-type) :plain-old-data-type (%resect:type-plain-old-data-p decl-type) + :explicit-copy-constructor (%resect:type-has-copy-constructor-p decl-type) + :deleted-copy-constructor (%resect:type-copy-constructor-deleted-p decl-type) + ;; Move assigment ops don't matter for our purposes. + :explicit-assignment (%resect:type-has-copy-assignment-p decl-type) + :deleted-assignment (%resect:type-copy-assignment-deleted-p decl-type) :abstract (%resect:record-abstract-p decl) :private (or (foreign-entity-private-p owner) (not (publicp decl)) diff --git a/src/spec/entity.lisp b/src/spec/entity.lisp index e65dc40..9d90329 100644 --- a/src/spec/entity.lisp +++ b/src/spec/entity.lisp @@ -26,6 +26,8 @@ #:foreign-entity-bit-alignment #:foreign-plain-old-data-type-p + #:foreign-type-assignable-p + #:foreign-type-copy-constructible-p #:foreign-entity-location @@ -277,12 +279,22 @@ :initform nil :reader foreign-plain-old-data-type-p))) +(defgeneric foreign-type-assignable-p (foreign-type)) + +(defgeneric foreign-type-copy-constructible-p (foreign-type)) + ;;; ;;; PRIMITIVE ;;; (defclass foreign-primitive (foreign-type) ()) +(defmethod foreign-type-assignable-p ((type foreign-primitive)) + t) + +(defmethod foreign-type-copy-constructible-p ((type foreign-primitive)) + t) + ;;; ;;; CONSTANT @@ -304,6 +316,13 @@ :initform nil :reader foreign-enum-type))) +(defmethod foreign-type-assignable-p ((type foreign-enum)) + t) + +(defmethod foreign-type-copy-constructible-p ((type foreign-enum)) + t) + + ;;; ;;; TEMPLATABLE ;;; @@ -409,7 +428,45 @@ :reader foreign-entity-private-p) (forward-p :initarg :forward :initform nil - :reader foreign-entity-forward-p))) + :reader foreign-entity-forward-p) + (has-explicit-assignment-p :initarg :explicit-assignment + :initform nil + :reader foreign-record-has-explicit-assignment-p) + (has-deleted-assignment-p :initarg :deleted-assignment + :initform nil + :reader foreign-record-has-deleted-assignment-p) + (has-explicit-copy-constructor-p :initarg :explicit-copy-constructor + :initform nil + :reader foreign-record-has-explicit-copy-constructor-p) + (has-deleted-copy-constructor-p :initarg :deleted-copy-constructor + :initform nil + :reader foreign-record-has-deleted-copy-constructor-p))) + +(defmethod foreign-type-assignable-p ((record foreign-record)) + (and (not (foreign-record-has-deleted-assignment-p record)) + (or (foreign-record-has-explicit-assignment-p record) + (and (every (lambda (field) + (let ((field-type (foreign-enveloped-entity field))) + ;; You can normally assign through a non-const reference, but + ;; C++ won't generate an implicit assignment operator that + ;; assigns to a reference field. + (or (and (foreign-type-assignable-p field-type) + (not (typep field-type 'foreign-reference))) + ;; Top-level arrays are not assignable, but as fields of records + ;; they become so, if their element type is. + (and (typep field-type 'foreign-array) + (foreign-type-assignable-p (foreign-enveloped-entity field-type)))))) + (foreign-record-fields record)) + (every #'foreign-type-assignable-p (foreign-record-parents record)))))) + +(defmethod foreign-type-copy-constructible-p ((record foreign-record)) + (and (not (foreign-record-has-deleted-copy-constructor-p record)) + (or (foreign-record-has-explicit-copy-constructor-p record) + (and (every (compose #'foreign-type-copy-constructible-p + #'foreign-enveloped-entity) + (foreign-record-fields record)) + (every #'foreign-type-copy-constructible-p + (foreign-record-parents record)))))) (defmethod foreign-entity-forward-p (any) @@ -450,6 +507,13 @@ :reader foreign-function-variadic-p))) +(defmethod foreign-type-assignable-p ((proto foreign-function-prototype)) + nil) + +(defmethod foreign-type-copy-constructible-p ((proto foreign-function-prototype)) + nil) + + (defclass foreign-function (declared identified named @@ -496,6 +560,12 @@ :location (foreign-entity-location entity) :enveloped value)) +(defmethod foreign-type-assignable-p ((alias foreign-alias)) + (foreign-type-assignable-p (foreign-enveloped-entity alias))) + +(defmethod foreign-type-copy-constructible-p ((alias foreign-alias)) + (foreign-type-copy-constructible-p (foreign-enveloped-entity alias))) + ;;; ;;; ARRAY @@ -516,6 +586,13 @@ :dimensions (foreign-array-dimensions entity) :enveloped value)) +(defmethod foreign-type-assignable-p ((array foreign-array)) + ;; Top-level arrays are not assignable, but see the method on `foreign-record'. + nil) + +(defmethod foreign-type-copy-constructible-p ((array foreign-array)) + (foreign-type-copy-constructible-p (foreign-enveloped-entity array))) + ;;; ;;; POINTER @@ -526,6 +603,13 @@ (defmethod rewrap-foreign-envelope ((entity foreign-pointer) value) (make-instance 'foreign-pointer :enveloped value)) +(defmethod foreign-type-assignable-p ((pointer foreign-pointer)) + t) + +(defmethod foreign-type-copy-constructible-p ((pointer foreign-pointer)) + t) + + ;;; ;;; REFERENCE ;;; @@ -540,6 +624,16 @@ :rvalue (foreign-reference-rvalue-p entity) :enveloped value)) +(defmethod foreign-type-assignable-p ((reference foreign-reference)) + (foreign-type-assignable-p (foreign-enveloped-entity reference))) + +(defmethod foreign-type-copy-constructible-p ((reference foreign-reference)) + ;; Technically, lvalue references aren't constructed, but as field types they don't prevent + ;; the containing record from being copy-constructible. + ;; Rvalue references can't be field types, but they also can't be copied. + (not (foreign-reference-rvalue-p reference))) + + ;;; ;;; VARIABLE ;;; @@ -565,6 +659,11 @@ ;;; (defclass foreign-const-qualifier (foreign-qualifier) ()) +(defmethod foreign-type-assignable-p ((qual foreign-const-qualifier)) + nil) + +(defmethod foreign-type-copy-constructible-p ((qual foreign-const-qualifier)) + (foreign-type-copy-constructible-p (foreign-enveloped-entity qual))) ;;; ;;; UNKNOWN From 0cad05e96f85c06240184114322ec29d9223319e Mon Sep 17 00:00:00 2001 From: "Scott L. Burson" Date: Thu, 13 Aug 2026 18:02:26 -0700 Subject: [PATCH 2/3] Untabify branch --- src/spec/entity.lisp | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/spec/entity.lisp b/src/spec/entity.lisp index 9d90329..da1dd8d 100644 --- a/src/spec/entity.lisp +++ b/src/spec/entity.lisp @@ -26,8 +26,8 @@ #:foreign-entity-bit-alignment #:foreign-plain-old-data-type-p - #:foreign-type-assignable-p - #:foreign-type-copy-constructible-p + #:foreign-type-assignable-p + #:foreign-type-copy-constructible-p #:foreign-entity-location @@ -430,43 +430,43 @@ :initform nil :reader foreign-entity-forward-p) (has-explicit-assignment-p :initarg :explicit-assignment - :initform nil - :reader foreign-record-has-explicit-assignment-p) + :initform nil + :reader foreign-record-has-explicit-assignment-p) (has-deleted-assignment-p :initarg :deleted-assignment - :initform nil - :reader foreign-record-has-deleted-assignment-p) + :initform nil + :reader foreign-record-has-deleted-assignment-p) (has-explicit-copy-constructor-p :initarg :explicit-copy-constructor - :initform nil - :reader foreign-record-has-explicit-copy-constructor-p) + :initform nil + :reader foreign-record-has-explicit-copy-constructor-p) (has-deleted-copy-constructor-p :initarg :deleted-copy-constructor - :initform nil - :reader foreign-record-has-deleted-copy-constructor-p))) + :initform nil + :reader foreign-record-has-deleted-copy-constructor-p))) (defmethod foreign-type-assignable-p ((record foreign-record)) (and (not (foreign-record-has-deleted-assignment-p record)) (or (foreign-record-has-explicit-assignment-p record) - (and (every (lambda (field) - (let ((field-type (foreign-enveloped-entity field))) - ;; You can normally assign through a non-const reference, but - ;; C++ won't generate an implicit assignment operator that - ;; assigns to a reference field. - (or (and (foreign-type-assignable-p field-type) - (not (typep field-type 'foreign-reference))) - ;; Top-level arrays are not assignable, but as fields of records - ;; they become so, if their element type is. - (and (typep field-type 'foreign-array) - (foreign-type-assignable-p (foreign-enveloped-entity field-type)))))) - (foreign-record-fields record)) - (every #'foreign-type-assignable-p (foreign-record-parents record)))))) + (and (every (lambda (field) + (let ((field-type (foreign-enveloped-entity field))) + ;; You can normally assign through a non-const reference, but + ;; C++ won't generate an implicit assignment operator that + ;; assigns to a reference field. + (or (and (foreign-type-assignable-p field-type) + (not (typep field-type 'foreign-reference))) + ;; Top-level arrays are not assignable, but as fields of records + ;; they become so, if their element type is. + (and (typep field-type 'foreign-array) + (foreign-type-assignable-p (foreign-enveloped-entity field-type)))))) + (foreign-record-fields record)) + (every #'foreign-type-assignable-p (foreign-record-parents record)))))) (defmethod foreign-type-copy-constructible-p ((record foreign-record)) (and (not (foreign-record-has-deleted-copy-constructor-p record)) (or (foreign-record-has-explicit-copy-constructor-p record) - (and (every (compose #'foreign-type-copy-constructible-p - #'foreign-enveloped-entity) - (foreign-record-fields record)) - (every #'foreign-type-copy-constructible-p - (foreign-record-parents record)))))) + (and (every (compose #'foreign-type-copy-constructible-p + #'foreign-enveloped-entity) + (foreign-record-fields record)) + (every #'foreign-type-copy-constructible-p + (foreign-record-parents record)))))) (defmethod foreign-entity-forward-p (any) From 8f41110393ad6c899731ef52a7bcc8b62bbe4840 Mon Sep 17 00:00:00 2001 From: "Scott L. Burson" Date: Thu, 13 Aug 2026 00:53:34 -0700 Subject: [PATCH 3/3] Special handling for 'std::unique_ptr' --- src/spec/entity.lisp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/spec/entity.lisp b/src/spec/entity.lisp index da1dd8d..3ca3bf0 100644 --- a/src/spec/entity.lisp +++ b/src/spec/entity.lisp @@ -443,7 +443,8 @@ :reader foreign-record-has-deleted-copy-constructor-p))) (defmethod foreign-type-assignable-p ((record foreign-record)) - (and (not (foreign-record-has-deleted-assignment-p record)) + (and (not (or (foreign-record-has-deleted-assignment-p record) + (foreign-type-known-non-copyable-p record))) (or (foreign-record-has-explicit-assignment-p record) (and (every (lambda (field) (let ((field-type (foreign-enveloped-entity field))) @@ -460,7 +461,8 @@ (every #'foreign-type-assignable-p (foreign-record-parents record)))))) (defmethod foreign-type-copy-constructible-p ((record foreign-record)) - (and (not (foreign-record-has-deleted-copy-constructor-p record)) + (and (not (or (foreign-record-has-deleted-copy-constructor-p record) + (foreign-type-known-non-copyable-p record))) (or (foreign-record-has-explicit-copy-constructor-p record) (and (every (compose #'foreign-type-copy-constructible-p #'foreign-enveloped-entity) @@ -468,6 +470,13 @@ (every #'foreign-type-copy-constructible-p (foreign-record-parents record)))))) +(defun foreign-type-known-non-copyable-p (record) + ;; We don't want to force the user to include these in their wrapper, so we special-case them. + (and (string= (foreign-entity-namespace record) "std") + (let ((name (foreign-entity-name record))) + (some (lambda (str) (string= name str :end1 (min (length str) (length name)))) + '("unique_ptr"))))) + (defmethod foreign-entity-forward-p (any) (declare (ignore any))