branch: externals/phpinspect
commit 692711e34dadda142bfc3649abdbec06c02f9e82
Author: Hugo Thunnissen <de...@hugot.nl>
Commit: Hugo Thunnissen <de...@hugot.nl>

    Implement property cells for more reliable property inheritance modeling
---
 phpinspect-buffer.el        |   4 +-
 phpinspect-eldoc.el         |  22 +++--
 phpinspect-method-cell.el   |   3 +
 phpinspect-property-cell.el | 210 ++++++++++++++++++++++++++++++++++++++++++++
 phpinspect-resolve.el       |   4 +-
 phpinspect-suggest.el       |  11 ++-
 phpinspect-typedef.el       | 140 +++++++++++++++++------------
 test/test-buffer.el         |  36 ++++----
 test/test-index.el          |  24 ++---
 test/test-typedef.el        |  92 +++++++++++++++++--
 10 files changed, 437 insertions(+), 109 deletions(-)

diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el
index dddb76b708..925bb16331 100644
--- a/phpinspect-buffer.el
+++ b/phpinspect-buffer.el
@@ -167,7 +167,7 @@ linked with."
            (when-let ((class (phpinspect-project-get-typedef
                               (phpinspect-buffer-project buffer)
                               (car var))))
-             (phpi-typedef-delete-variable class (cdr var)))))
+             (phpi-typedef-delete-property class (cdr var)))))
         ((phpinspect-function-p token)
          (when-let ((func (gethash token (phpinspect-buffer-token-index 
buffer))))
            (remhash token (phpinspect-buffer-token-index buffer))
@@ -498,7 +498,7 @@ linked with."
                    class-obj (phpinspect-buffer-project buffer)
                    (cadr (phpinspect-meta-token var)) type-resolver class)))
 
-          (phpi-typedef-set-variable class-obj indexed)
+          (phpi-typedef-set-property class-obj indexed)
 
           (phpinspect-buffer-set-index-reference-for-token
            buffer (phpinspect-meta-token var)
diff --git a/phpinspect-eldoc.el b/phpinspect-eldoc.el
index 7974f3496d..91b3215a84 100644
--- a/phpinspect-eldoc.el
+++ b/phpinspect-eldoc.el
@@ -91,11 +91,11 @@ be implemented for return values of 
`phpinspect-eld-strategy-execute'")
             variable method result)
         (when attribute-name
           (cond ((phpinspect-static-attrib-p attrib)
-                 (setq variable (phpi-typedef-get-variable class 
attribute-name))
+                 (setq variable (phpi-typedef-get-property class 
attribute-name))
 
                  (if (and variable
-                          (or (phpinspect--variable-static-p variable)
-                              (phpinspect--variable-const-p variable)))
+                          (or (phpi-prop-static-p variable)
+                              (phpi-prop-const-p variable)))
                      (setq result variable)
                    (setq method (phpi-typedef-get-static-method
                                  class (phpinspect-intern-name 
attribute-name)))
@@ -105,7 +105,7 @@ be implemented for return values of 
`phpinspect-eld-strategy-execute'")
                  (setq variable (phpi-typedef-get-variable class 
attribute-name))
 
                  (if (and variable
-                          (phpinspect--variable-vanilla-p variable))
+                          (phpi-prop-vanilla-p variable))
                      (setq result variable)
                    (setq method (phpi-typedef-get-method
                                  class (phpinspect-intern-name 
attribute-name)))
@@ -219,14 +219,20 @@ be implemented for return values of 
`phpinspect-eld-strategy-execute'")
           (when func
             (phpinspect-make-function-doc :fn func :arg-pos arg-pos))))))))
 
-(cl-defmethod phpinspect-eldoc-string ((var phpinspect--variable))
+(defun phpinspect-var-eldoc-string (prop-or-var)
   (concat (truncate-string-to-width
-           (propertize (concat (if (phpinspect--variable-vanilla-p var) "$" "")
-                               (phpinspect--variable-name var))
+           (propertize (concat (if (phpi-var-vanilla-p var) "$" "")
+                               (phpi-var-name var))
                        'face 'font-lock-variable-name-face)
            phpinspect-eldoc-word-width)
           ": "
-          (phpinspect--display-format-type-name (phpinspect--variable-type 
var))))
+          (phpinspect--display-format-type-name (phpi-var-type var))))
+
+(cl-defmethod phpinspect-eldoc-string ((var phpinspect--variable))
+  (phpinspect-var-eldoc-string var))
+
+(cl-defmethod phpinspect-eldoc-string ((prop phpinspect-property))
+  (phpinspect-var-eldoc-string prop))
 
 (cl-defstruct (phpinspect-function-doc (:constructor 
phpinspect-make-function-doc))
   (fn nil
diff --git a/phpinspect-method-cell.el b/phpinspect-method-cell.el
index d52ef1ef83..f460eb8582 100644
--- a/phpinspect-method-cell.el
+++ b/phpinspect-method-cell.el
@@ -21,6 +21,9 @@
 
 ;;; Commentary:
 
+;; This file contains struct definitions and logic for the management of
+;; (collections of) methods associated with a specific type.
+
 ;;; Code:
 
 (require 'cl-macs)
diff --git a/phpinspect-property-cell.el b/phpinspect-property-cell.el
new file mode 100644
index 0000000000..70c07abb38
--- /dev/null
+++ b/phpinspect-property-cell.el
@@ -0,0 +1,210 @@
+;;; phpinspect-property-cell.el --- Models for PHP property definitions  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2021-2023  Free Software Foundation, Inc
+
+;; Author: Hugo Thunnissen <de...@hugot.nl>
+;; Keywords: php, languages, tools, convenience
+;; Version: 1.2.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; There's a good amount of code from phpinspect-method-cell.el copied over to
+;; this one with small modifications to cater to class properties instead of
+;; methods. The alternative would have been to create some kind of reusable
+;; generic implementation, but the subtle differences in the ways property
+;; inheritance and method inheritance work makes this more complicated than 
it's
+;; worth.
+
+;;; Code:
+
+(require 'cl-macs)
+(require 'phpinspect-type)
+
+(cl-defstruct (phpinspect-property
+              (:constructor phpinspect-make-property-generated)
+              (:conc-name phpi-prop-))
+  (origin-type nil
+              :type phpinspect--type)
+  (name nil
+       :type phpinspect-name)
+  (definition nil
+             :type phpinspect--variable))
+
+(defun phpinspect-make-property (origin-type definition)
+  (phpinspect-make-property-generated
+   :name (phpinspect-intern-name (phpinspect--variable-name definition))
+   :origin-type origin-type
+   :definition definition))
+
+(cl-defstruct (phpinspect-property-cell
+              (:constructor phpinspect-make-property-cell)
+              (:conc-name phpi-pc-))
+  (own nil
+       :type phpinspect-property)
+  (inherited nil
+            :type phpinspect-property))
+
+(cl-defstruct(phpinspect-property-collection
+             (:constructor phpinspect-make-property-collection)
+             (:conc-name phpi-pcol-))
+  (home-type nil
+            :type phpinspect--type)
+  (cells nil
+        :type list
+        :documentation "<string,phpinspect-property-cell>"))
+
+(defun phpi-pcol-find-cell (pcol prop-name &optional remove)
+  (cl-assert (phpinspect-name-p prop-name))
+
+  (alist-get prop-name (phpi-pcol-cells pcol) nil remove #'eq))
+
+(defun phpi-pcol-find-cell-create (pcol prop-name)
+  (let ((cell (phpi-pcol-find-cell pcol prop-name)))
+    (unless cell
+      (setq cell (phpinspect-make-property-cell))
+      (push (cons prop-name cell) (phpi-pcol-cells pcol)))
+
+    cell))
+
+(defun phpi-prop-inheritable-p (prop)
+  (and-let* ((var (phpi-prop-definition prop))
+            (scope (phpinspect--variable-scope var))
+            ((or (phpinspect-protected-p scope)
+                 (phpinspect-public-p scope))))))
+
+(defun phpi-pc-set (cell home-type origin-type property)
+  (if (phpinspect--type= home-type origin-type)
+      (setf (phpi-pc-own cell) property)
+    ;; Prop is not from home-type, only add if it can be inherited.
+    (when (or (not property) ;; Deletion, always allowed
+             (phpi-prop-inheritable-p property)) ;; Update, only when 
inheritable
+      (setf (phpi-pc-inherited cell) property))))
+
+(defun phpi-pcol-add (pcol property)
+  (let ((cell (phpi-pcol-find-cell-create pcol (phpi-prop-name property))))
+    (phpi-pc-set
+     cell (phpi-pcol-home-type pcol) (phpi-prop-origin-type property) 
property)))
+
+(defun phpi-pcol-list-active (pcol)
+  (let (active)
+    (dolist (cons (phpi-pcol-cells pcol))
+      (when-let ((prop (phpi-pc-get-active (cdr cons))))
+       (push prop active)))
+
+    active))
+
+(defun phpi-pc-empty-p (cell)
+  "CELL is empty when it contains no methods."
+  (not (phpi-pc-get-active cell)))
+
+(defun phpi-pc-get-active (cell)
+  "Get active property from CELL, according to PHP precendence order."
+  (or (phpi-pc-own cell)
+      (phpi-pc-inherited cell)))
+
+(defun phpi-pcol-get-active-property (mcol name)
+  (when-let ((cell (phpi-pcol-find-cell mcol name)))
+    (phpi-pc-get-active cell)))
+
+(defun phpi-pcol-list-own (pcol)
+  (let (list)
+    (dolist (cons (phpi-pcol-cells pcol))
+      (when-let ((prop (phpi-pc-own (cdr cons))))
+        (push prop list)))
+    list))
+
+(defun phpi-pcol-set-home-type (pcol type)
+  "Set home type of PCOL to TYPE.
+
+Also updates all members of PCOL with the same origin-type."
+  (setf (phpi-pcol-home-type pcol) type)
+  (dolist (prop (phpi-pcol-list-own pcol))
+    (setf (phpi-prop-origin-type prop) type)))
+
+(defun phpi-pc-get-for-type (cell type)
+  (cl-block try-get
+    (dolist (prop (list (phpi-pc-own cell)
+                       (phpi-pc-inherited cell)))
+      (when (and prop (phpinspect--type= type (phpi-prop-origin-type prop)))
+       (cl-return-from try-get prop)))))
+
+(defun phpi-pcol-delete (pcol prop-name)
+  (phpi-pcol-find-cell pcol prop-name t))
+
+(defun phpi-pcol-delete-for-type (pcol type &optional name)
+
+    "Delete from PCOL all properties that originate from TYPE.
+
+When NAME is provided, only property with NAME is deleted."
+  (if name
+      ;; Name is provided, only delete method with NAME.
+      (when-let ((cell (phpi-pcol-find-cell pcol name)))
+        (when (phpi-pc-get-for-type cell type)
+          (phpi-pc-set cell (phpi-pcol-home-type pcol) type nil))
+
+        (when (phpi-pc-empty-p cell)
+          (phpi-pcol-delete pcol name)))
+
+    (let ((cells (phpi-pcol-cells pcol)))
+      (dolist (cons cells)
+        (let ((cell (cdr cons)))
+          (when (phpi-pc-get-for-type cell type)
+            (phpi-pc-set cell (phpi-pcol-home-type pcol) type nil))
+
+          ;; mark cell for deletion by setting car and cdr of alist member to
+          ;; nil
+          (when (phpi-pc-empty-p cell)
+            (setcar cons nil)
+            (setcdr cons nil))))
+      ;; Delete all empty cells
+      (setf (phpi-pcol-cells pcol) (delete (cons nil nil) cells)))))
+
+(defun phpi-prop-static-p (prop)
+  (phpinspect--variable-static-p (phpi-prop-definition prop)))
+
+(defun phpi-prop-const-p (prop)
+  (phpinspect--variable-const-p (phpi-prop-definition prop)))
+
+(defun phpi-prop-vanilla-p (prop)
+  (phpinspect--variable-vanilla-p (phpi-prop-definition prop)))
+
+(defun phpi-prop-type (prop)
+  (phpinspect--variable-type (phpi-prop-definition prop)))
+
+(defun phpi-prop-scope (prop)
+  (phpinspect--variable-scope (phpi-prop-definition prop)))
+
+;; helper functions
+(cl-defmethod phpi-var-type ((var phpinspect--variable))
+  (phpinspect--variable-type var))
+
+(cl-defmethod phpi-var-type ((prop phpinspect-property))
+  (phpi-prop-type prop))
+
+(cl-defmethod phpi-var-name ((var phpinspect--variable))
+  (phpinspect--variable-name var))
+
+(cl-defmethod phpi-var-name ((prop phpinspect-property))
+  (phpinspect-name-string (phpi-prop-name prop)))
+
+(cl-defmethod phpi-var-vanilla-p ((variable phpinspect--variable))
+  (phpinspect--variable-vanilla-p variable))
+
+(cl-defmethod phpi-var-vanilla-p ((prop phpinspect-property))
+  (phpi-prop-vanilla-p prop))
+
+(provide 'phpinspect-property-cell)
+;;; phpinspect-property-cell.el ends here
diff --git a/phpinspect-resolve.el b/phpinspect-resolve.el
index f27cc4bcb6..7bafaeb7b6 100644
--- a/phpinspect-resolve.el
+++ b/phpinspect-resolve.el
@@ -178,11 +178,11 @@ Destructively removes tokens from the end of 
ASSIGNMENT-TOKENS."
   (phpinspect--log "Getting cached project class variable type %s::%s"
                    class-fqn variable-name)
   (let ((found-variable
-         (phpi-typedef-get-variable
+         (phpi-typedef-get-property
           (phpinspect-rctx-get-typedef rctx class-fqn)
           variable-name)))
     (when found-variable
-      (phpinspect--variable-type found-variable))))
+      (phpi-var-type found-variable))))
 
 (defsubst phpinspect-get-cached-project-typedef-static-method-type
   (rctx class-fqn method-name)
diff --git a/phpinspect-suggest.el b/phpinspect-suggest.el
index d6f8c56240..be5a59bc13 100644
--- a/phpinspect-suggest.el
+++ b/phpinspect-suggest.el
@@ -94,12 +94,11 @@
       (progn (phpinspect--log "Failed to find methods for class %s :(" class) 
nil)))
 
 (defun phpinspect--get-variables-for-class (rctx class-name &optional static)
-  (let ((class (phpinspect-rctx-get-typedef
-                rctx class-name 'no-enqueue)))
+  (let ((class (phpinspect-rctx-get-typedef rctx class-name 'no-enqueue)))
     (when class
       (if static
-          (append (phpi-typedef-get-static-variables class) 
(phpi-typedef-get-constants class))
-        (phpi-typedef-get-variables class)))))
+          (append (phpi-typedef-get-static-properties class) 
(phpi-typedef-get-constants class))
+        (phpi-typedef-get-properties class)))))
 
 (defun phpinspect--make-method-lister (resolvecontext &optional static)
   (lambda (fqn)
@@ -114,6 +113,10 @@
 (cl-defmethod phpinspect--candidate-scope ((candidate phpinspect--variable))
   (phpinspect--variable-scope candidate))
 
+(cl-defmethod phpinspect--candidate-scope ((candidate phpinspect-property))
+  (phpi-prop-scope candidate))
+
+
 (defun phpinspect-suggest-attributes-at-point
     (resolvecontext &optional static)
   "Suggest object or class attributes at point.
diff --git a/phpinspect-typedef.el b/phpinspect-typedef.el
index 5106b2a0a7..58bce5cfd2 100644
--- a/phpinspect-typedef.el
+++ b/phpinspect-typedef.el
@@ -23,6 +23,8 @@
 
 ;;; Code:
 (require 'phpinspect-method-cell)
+(require 'phpinspect-property-cell)
+(require 'phpinspect-type)
 
 (cl-defstruct (phpinspect-typedef
                (:constructor phpinspect-make-typedef-generated)
@@ -45,8 +47,8 @@ non-nil value.")
            :type phpinspect-method-collection)
   (static-methods nil
                   :type phpinspect-method-collection)
-  (variables nil
-             :type list)
+  (properties nil
+              :type list)
   (initial-index nil
                  :type bool)
   (trait-config nil
@@ -76,6 +78,7 @@ non-nil value.")
   (let ((def (phpinspect-make-typedef-generated
               :name type
               :retriever retriever
+             :properties (phpinspect-make-property-collection :home-type type)
               :methods (phpinspect-make-method-collection :home-type type)
               :static-methods (phpinspect-make-method-collection :home-type 
type)
               :method-adder (phpinspect-make-method-adder))))
@@ -114,6 +117,8 @@ Conditionally executes BODY depending on
 (cl-defstruct (phpinspect-method-adder
                (:constructor phpinspect-make-method-adder)
                (:conc-name phpi-ma-))
+  "The method adder adds methods to a method-collection, aliasing
+and/or overriding methods when appropriate."
   (aliases nil
            :type alist)
   (overrides nil
@@ -251,14 +256,19 @@ Note: this function returns all types found in CONFIG."
   "Add methods and properties to DEF from FOREIGN-DEF."
   (phpi--typedef-edit def
     (when foreign-def
-      (dolist (method (phpi-typedef-get-methods foreign-def))
-        (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-methods 
def) method))
+      (let ((ma (phpi-typedef-method-adder def))
+           (methods (phpi-typedef-methods def))
+           (st-methods (phpi-typedef-static-methods def))
+           (props (phpi-typedef-properties def)))
 
-      (dolist (method (phpi-typedef-get-static-methods foreign-def))
-        (phpi-ma-add (phpi-typedef-method-adder def) 
(phpi-typedef-static-methods def) method))
+       (dolist (method (phpi-typedef-get-methods foreign-def))
+          (phpi-ma-add ma methods method))
 
-      (dolist (var (phpi-typedef-variables foreign-def))
-        (phpi-typedef-set-variable def var)))))
+       (dolist (method (phpi-typedef-get-static-methods foreign-def))
+          (phpi-ma-add ma st-methods method))
+
+       (dolist (prop (phpi-pcol-list-active (phpi-typedef-properties 
foreign-def)))
+         (phpi-pcol-add props prop))))))
 
 (defun phpi-typedef-subscribe-to-foreign-typedef (def foreign-def)
   (phpi--typedef-edit def
@@ -280,11 +290,10 @@ extended classes, used traits or implemented interfaces."
                      :test #'phpinspect--type=))
 
     ;; Remove all members of foreign typedef
-    (phpi-mcol-delete-for-type (phpi-typedef-methods def) (phpi-typedef-name 
foreign-def))
-    (phpi-mcol-delete-for-type (phpi-typedef-static-methods def) 
(phpi-typedef-name foreign-def))
-    (dolist (variable (phpi-typedef-variables foreign-def))
-      (phpi-typedef-delete-variable def variable))))
-
+    (let ((ft (phpi-typedef-name foreign-def)))
+      (phpi-mcol-delete-for-type (phpi-typedef-methods def) ft)
+      (phpi-mcol-delete-for-type (phpi-typedef-static-methods def) ft)
+      (phpi-pcol-delete-for-type (phpi-typedef-properties def) ft))))
 
 (defun phpi-typedef-trigger-subscriber-update (def)
   "Incorporate DEF into subscribed typedefs."
@@ -304,6 +313,21 @@ If STATIC is non-nil, updates static method."
           (phpi-typedef-set-static-method foreign-def method)
         (phpi-typedef-set-method foreign-def method)))))
 
+(defun phpi-typedef-trigger-subscriber-property-update (def property)
+  "Update downstream subscribers of DEF by setting PROPERTY."
+  (dolist (type (phpi-typedef-subscribed-types def))
+    (when-let ((foreign-def (phpi--typedef-get-foreign-type def type)))
+      (phpi-typedef-set-property foreign-def property))))
+
+(defun phpi-typedef-trigger-subscriber-property-delete (def origin-type 
property-name)
+  "Update subscribers of DEF by deleting PROPERTY-NAME of origin TYPE."
+  (dolist (subtype (phpi-typedef-subscribed-types def))
+    (when-let ((foreign-def (phpi--typedef-get-foreign-type def subtype)))
+      (phpi--typedef-edit foreign-def
+       (phpi-pcol-delete-for-type
+        (phpi-typedef-properties foreign-def) origin-type property-name)
+       (phpi-typedef-trigger-subscriber-property-delete foreign-def 
origin-type property-name)))))
+
 (defun phpi-typedef-trigger-subscriber-method-delete (def type method-name 
&optional static)
   "Update subscribers of DEF by deleting METHOD-NAME of origin TYPE."
   (dolist (subtype (phpi-typedef-subscribed-types def))
@@ -327,7 +351,8 @@ TYPE must be a structure of type `phpinspect--type'."
       (unless (phpinspect--type= existing type)
         (setf (phpi-typedef-name def) type)
         (phpi-mcol-set-home-type (phpi-typedef-methods def) type)
-        (phpi-mcol-set-home-type (phpi-typedef-static-methods def) type)))))
+        (phpi-mcol-set-home-type (phpi-typedef-static-methods def) type)
+       (phpi-pcol-set-home-type (phpi-typedef-properties def) type)))))
 
 (defun phpi-typedef-update-declaration (def declaration imports namespace-name 
trait-config)
   "Update declaration of DEF.
@@ -392,18 +417,15 @@ them, which are then incorporated into DEF's properties."
     (let ((ma (phpi-typedef-method-adder def))
           (mcol (phpi-typedef-methods def))
           (stmcol (phpi-typedef-static-methods def))
-          (home-type (phpi-typedef-name def)))
+          (home-type (phpi-typedef-name def))
+         (pcol (phpi-typedef-properties def)))
 
     ;; Override methods when class seems syntactically correct (has balanced 
braces)
       (when (alist-get 'complete index)
-        ;; Delete all known own methods
+        ;; Delete all known own methods and properties
         (phpi-mcol-delete-for-type mcol home-type)
         (phpi-mcol-delete-for-type stmcol home-type)
-
-        ;; FIXME: delete variables. This will require some way to keep track of
-        ;; a variables origin as we would otherwise risk deleting variables 
that
-        ;; originate from extended classes.
-        )
+       (phpi-pcol-delete-for-type pcol home-type))
 
       (dolist (method (alist-get 'methods index))
         (phpi-ma-add ma mcol (phpinspect-make-method home-type method) 
'overwrite))
@@ -413,10 +435,9 @@ them, which are then incorporated into DEF's properties."
 
       (setf (phpi-typedef-initial-index def) t)
 
-      (setf (phpi-typedef-variables def)
-            (append (alist-get 'variables index)
-                    (alist-get 'constants index)
-                    (alist-get 'static-variables index)))
+      (let-alist index
+       (dolist (var (append .variables .constants .static-variables))
+         (phpi-pcol-add pcol (phpinspect-make-property home-type var))))
 
       (phpi-typedef-update-extensions
        def `(,@(alist-get 'implements index)
@@ -433,37 +454,45 @@ them, which are then incorporated into DEF's properties."
   ((def phpinspect-typedef) (method-name (head phpinspect-name)) &optional 
tryhard)
   (phpi-mcol-get-return-type (phpi-typedef-static-methods def) method-name 
tryhard))
 
-(cl-defmethod phpi-typedef-set-variable ((def phpinspect-typedef)
+(cl-defmethod phpi-typedef-set-property ((def phpinspect-typedef) (prop 
phpinspect-property))
+  (phpi--typedef-edit def
+    (phpi-pcol-add (phpi-typedef-properties def) prop)
+    (phpi-typedef-trigger-subscriber-property-update def prop)))
+
+(cl-defmethod phpi-typedef-set-property ((def phpinspect-typedef)
                                          (var phpinspect--variable))
+  (phpi-typedef-set-property
+   def (phpinspect-make-property (phpi-typedef-name def) var)))
+
+(cl-defmethod phpi-typedef-delete-property ((def phpinspect-typedef)
+                                           (name (head phpinspect-name))
+                                           &optional origin-type)
   (phpi--typedef-edit def
-    (push var (phpi-typedef-variables def))))
+    (phpi-pcol-delete-for-type
+     (phpi-typedef-properties def) (phpi-typedef-name def) name)
+    (phpi-typedef-trigger-subscriber-property-delete
+     def (or origin-type (phpi-typedef-name def)) name)))
 
-(cl-defmethod phpi-typedef-delete-variable ((def phpinspect-typedef)
+(cl-defmethod phpi-typedef-delete-property ((def phpinspect-typedef)
                                             (var phpinspect--variable))
-  (phpi--typedef-edit def
-    (let ((first-half (seq-take-while (lambda (clvar) (not (eq var clvar)))
-                                      (phpi-typedef-variables def))))
-      ;; Only remove the first occurrence. If a parent class or a trait has the
-      ;; same variable, it will stick around.
-      (setf (phpi-typedef-variables def)
-            (append first-half (nthcdr (+ 1 (length first-half))
-                                       (phpi-typedef-variables def)))))))
-
-(cl-defmethod phpi-typedef-get-variables ((class phpinspect-typedef))
-  (seq-filter #'phpinspect--variable-vanilla-p (phpi-typedef-variables class)))
-
-(cl-defmethod phpi-typedef-get-static-variables ((class phpinspect-typedef))
-  (seq-filter #'phpinspect--variable-static-p (phpi-typedef-variables class)))
-
-(cl-defmethod phpi-typedef-get-constants ((class phpinspect-typedef))
-  (seq-filter #'phpinspect--variable-const-p (phpi-typedef-variables class)))
-
-(cl-defmethod phpi-typedef-get-variable
-  ((def phpinspect-typedef) (variable-name string))
-  (catch 'found
-    (dolist (variable (phpi-typedef-variables def))
-      (when (string= variable-name (phpinspect--variable-name variable))
-        (throw 'found variable)))))
+  (phpi-typedef-delete-property def (phpinspect-intern-name 
(phpi-variable-name))))
+
+(cl-defmethod phpi-typedef-get-properties ((def phpinspect-typedef))
+  (seq-filter #'phpi-prop-vanilla-p
+             (phpi-pcol-list-active (phpi-typedef-properties def))))
+
+(cl-defmethod phpi-typedef-get-static-properties ((class phpinspect-typedef))
+  (seq-filter #'phpi-prop-static-p
+             (phpi-pcol-list-active (phpi-typedef-properties def))))
+
+(cl-defmethod phpi-typedef-get-constants ((def phpinspect-typedef))
+  (seq-filter #'phpi-prop-const-p
+             (phpi-pcol-list-active (phpi-typedef-properties def))))
+
+(cl-defmethod phpi-typedef-get-property
+  ((def phpinspect-typedef) (prop-name string))
+  (phpi-pcol-get-active-property
+   (phpi-typedef-properties def) (phpinspect-intern-name prop-name)))
 
 (defun phpi-typedef-get-dependencies (def)
   "Gets types that DEF directly depends on.
@@ -476,14 +505,13 @@ Return value is a list if structures of the type
         (when (phpi-method-return-type method)
           (push (phpi-method-return-type method) deps)))
 
-
       (dolist (method (phpi-typedef-get-static-methods def))
         (when (phpi-method-return-type method)
           (push (phpi-method-return-type method) deps)))
 
-      (dolist (var (phpi-typedef-variables def))
-        (when (phpinspect--variable-type var)
-          (push (phpinspect--variable-type var) deps)))
+      (dolist (prop (phpi-pcol-list-active (phpi-typedef-properties def)))
+        (when (phpi-prop-type prop)
+          (push (phpi-prop-type prop) deps)))
 
       (phpinspect--types-uniq deps))))
 
diff --git a/test/test-buffer.el b/test/test-buffer.el
index 3b99595c80..e5bfa16c10 100644
--- a/test/test-buffer.el
+++ b/test/test-buffer.el
@@ -549,7 +549,7 @@ class TestClass
              (phpinspect-buffer-project buffer)
              (phpinspect--make-type :name "\\TestClass")))
 
-    (should (= 2 (length (phpi-typedef-variables
+    (should (= 1 (length (phpi-typedef-get-properties
                           (phpinspect-project-get-typedef
                            (phpinspect-buffer-project buffer)
                            (phpinspect--make-type :name "\\TestClass"))))))
@@ -561,8 +561,8 @@ class TestClass
                            (phpinspect--make-type :name "\\TestClass"))))))
 
     (should (phpinspect--type= (phpinspect--make-type :name "\\array")
-                              (phpinspect--variable-type
-                               (phpi-typedef-get-variable
+                              (phpi-var-type
+                               (phpi-typedef-get-property
                                 (phpinspect-project-get-typedef
                                  (phpinspect-buffer-project buffer)
                                  (phpinspect--make-type :name "\\TestClass"))
@@ -626,12 +626,12 @@ class AccountStatisticsController {
                      :fully-qualified t))))
         (should class)
 
-        (let ((model (phpi-typedef-get-variable class "model"))
-              (priv-model (phpi-typedef-get-variable class "privModel"))
+        (let ((model (phpi-typedef-get-property class "model"))
+              (priv-model (phpi-typedef-get-property class "privModel"))
               ;; Static variables are stored with "$" prefix
-              (relation (phpi-typedef-get-variable class "$relation"))
-              (static-relation (phpi-typedef-get-variable class 
"$staticRelation"))
-              (relations (phpi-typedef-get-variable class "relations")))
+              (relation (phpi-typedef-get-property class "$relation"))
+              (static-relation (phpi-typedef-get-property class 
"$staticRelation"))
+              (relations (phpi-typedef-get-property class "relations")))
           (should model)
           (should priv-model)
           (should relation)
@@ -646,19 +646,19 @@ class AccountStatisticsController {
                              :fully-qualified t))
                 (array-type (phpinspect--make-type :name "\\array" 
:fully-qualified t)))
 
-            (should (phpinspect--variable-type model))
-            (should (phpinspect--type= model-type (phpinspect--variable-type 
model)))
-            (should (phpinspect--variable-type priv-model))
-            (should (phpinspect--type= model-type (phpinspect--variable-type 
priv-model)))
-            (should (phpinspect--variable-type relation))
-            (should (phpinspect--type= relation-type 
(phpinspect--variable-type relation)))
-            (should (phpinspect--variable-type static-relation))
-            (should (phpinspect--type= relation-type 
(phpinspect--variable-type static-relation)))
+            (should (phpi-var-type model))
+            (should (phpinspect--type= model-type (phpi-var-type model)))
+            (should (phpi-var-type priv-model))
+            (should (phpinspect--type= model-type (phpi-var-type priv-model)))
+            (should (phpi-var-type relation))
+            (should (phpinspect--type= relation-type (phpi-var-type relation)))
+            (should (phpi-var-type static-relation))
+            (should (phpinspect--type= relation-type (phpi-var-type 
static-relation)))
 
-            (should (phpinspect--type= array-type (phpinspect--variable-type 
relations)))
+            (should (phpinspect--type= array-type (phpi-var-type relations)))
             (should (phpinspect--type=
                      relation-type
-                     (phpinspect--type-contains (phpinspect--variable-type 
relations))))))))))
+                     (phpinspect--type-contains (phpi-var-type 
relations))))))))))
 
 
 (ert-deftest phpinspect-buffer-parse-incrementally-unfinished-variable-scope ()
diff --git a/test/test-index.el b/test/test-index.el
index 9e511ccc7a..2a61580fcd 100644
--- a/test/test-index.el
+++ b/test/test-index.el
@@ -359,11 +359,11 @@ class AccountStatisticsController {
                      :fully-qualified t))))
         (should class)
 
-        (let ((model (phpi-typedef-get-variable class "model"))
-              (priv-model (phpi-typedef-get-variable class "privModel"))
+        (let ((model (phpi-typedef-get-property class "model"))
+              (priv-model (phpi-typedef-get-property class "privModel"))
               ;; Static variables are stored with "$" prefix
-              (relation (phpi-typedef-get-variable class "$relation"))
-              (static-relation (phpi-typedef-get-variable class 
"$staticRelation")))
+              (relation (phpi-typedef-get-property class "$relation"))
+              (static-relation (phpi-typedef-get-property class 
"$staticRelation")))
           (should model)
           (should priv-model)
           (should relation)
@@ -376,14 +376,14 @@ class AccountStatisticsController {
                              :name 
"\\Illuminate\\Database\\Eloquent\\Relations\\Relation"
                              :fully-qualified t)))
 
-            (should (phpinspect--variable-type model))
-            (should (phpinspect--type= model-type (phpinspect--variable-type 
model)))
-            (should (phpinspect--variable-type priv-model))
-            (should (phpinspect--type= model-type (phpinspect--variable-type 
priv-model)))
-            (should (phpinspect--variable-type relation))
-            (should (phpinspect--type= relation-type 
(phpinspect--variable-type relation)))
-            (should (phpinspect--variable-type static-relation))
-            (should (phpinspect--type= relation-type 
(phpinspect--variable-type static-relation)))))))))
+            (should (phpi-var-type model))
+            (should (phpinspect--type= model-type (phpi-var-type model)))
+            (should (phpi-var-type priv-model))
+            (should (phpinspect--type= model-type (phpi-var-type priv-model)))
+            (should (phpi-var-type relation))
+            (should (phpinspect--type= relation-type (phpi-var-type relation)))
+            (should (phpi-var-type static-relation))
+            (should (phpinspect--type= relation-type (phpi-var-type 
static-relation)))))))))
 
 
 (ert-deftest phpinspect-index-return-type-annotation-for-method ()
diff --git a/test/test-typedef.el b/test/test-typedef.el
index 86687a78df..f9805b54ca 100644
--- a/test/test-typedef.el
+++ b/test/test-typedef.el
@@ -161,17 +161,17 @@
 (ert-deftest phpinspect-typedef-variables ()
   (let ((def (phpinspect-make-typedef (phpinspect--make-type :name "\\test"))))
 
-    (phpi-typedef-set-variable def (phpinspect--make-variable :name "test"))
-    (phpi-typedef-set-variable def (phpinspect--make-variable :name "test2"))
+    (phpi-typedef-set-property def (phpinspect--make-variable :name "test"))
+    (phpi-typedef-set-property def (phpinspect--make-variable :name "test2"))
 
-    (let ((test1 (phpi-typedef-get-variable def "test"))
-          (test2 (phpi-typedef-get-variable def "test2")))
+    (let ((test1 (phpi-typedef-get-property def "test"))
+          (test2 (phpi-typedef-get-property def "test2")))
 
       (should test1)
-      (should (string= "test" (phpinspect--variable-name test1)))
+      (should (string= "test" (phpi-var-name test1)))
 
-      (should (phpi-typedef-get-variable def "test2"))
-      (should (string= "test2" (phpinspect--variable-name test2))))))
+      (should (phpi-typedef-get-property def "test2"))
+      (should (string= "test2" (phpi-var-name test2))))))
 
 
 (ert-deftest phpinspect-typedef-set-index-trait ()
@@ -247,3 +247,81 @@
       (should (string= "boo" (phpi-fn-name method)))
       (should (phpinspect--type= (phpinspect--make-type :name "\\string")
                                  (phpi-fn-return-type method))))))
+
+(ert-deftest phpinspect-typedef-inherited-properties ()
+  (let* ((def1 (phpinspect-make-typedef (phpinspect--make-type :name "\\A")))
+         (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\B")))
+         (def3 (phpinspect-make-typedef (phpinspect--make-type :name "\\C")))
+         (retriever (lambda (type)
+                      (cond ((phpinspect--type= type (phpi-typedef-name def1))
+                             def1)
+                            ((phpinspect--type= type (phpi-typedef-name def2))
+                             def2)
+                            ((phpinspect--type= type (phpi-typedef-name def3))
+                             def3)))))
+
+    (setf (phpi-typedef-retriever def1) retriever
+          (phpi-typedef-retriever def2) retriever
+          (phpi-typedef-retriever def3) retriever)
+
+
+    (phpi-typedef-update-extensions def1 (list (phpi-typedef-name def2)))
+    (phpi-typedef-update-extensions def2 (list (phpi-typedef-name def3)))
+
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testPublic" :scope '(:public)))
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testProtected" :scope '(:protected)))
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testPrivate" :scope '(:private)))
+    (phpi-typedef-trigger-subscriber-update def3)
+
+    (let ((prop (phpi-typedef-get-property def1 "testPublic")))
+      (should prop)
+      (should-not (phpi-prop-type prop)))
+
+    (let ((prop (phpi-typedef-get-property def1 "testProtected")))
+      (should prop)
+      (should-not (phpi-prop-type prop)))
+
+    (let ((prop (phpi-typedef-get-property def1 "testPrivate")))
+      (should-not prop))))
+
+(ert-deftest phpinspect-typedef-inherited-properties-no-manual-trigger ()
+  (let* ((def1 (phpinspect-make-typedef (phpinspect--make-type :name "\\A")))
+         (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\B")))
+         (def3 (phpinspect-make-typedef (phpinspect--make-type :name "\\C")))
+         (retriever (lambda (type)
+                      (cond ((phpinspect--type= type (phpi-typedef-name def1))
+                             def1)
+                            ((phpinspect--type= type (phpi-typedef-name def2))
+                             def2)
+                            ((phpinspect--type= type (phpi-typedef-name def3))
+                             def3)))))
+
+    (setf (phpi-typedef-retriever def1) retriever
+          (phpi-typedef-retriever def2) retriever
+          (phpi-typedef-retriever def3) retriever)
+
+
+    (phpi-typedef-update-extensions def1 (list (phpi-typedef-name def2)))
+    (phpi-typedef-update-extensions def2 (list (phpi-typedef-name def3)))
+
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testPublic" :scope '(:public)))
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testProtected" :scope '(:protected)))
+    (phpi-typedef-set-property def3 (phpinspect--make-variable :name 
"testPrivate" :scope '(:private)))
+
+    ;; Don't manually trigger subscriber update
+
+    (let ((prop (phpi-typedef-get-property def1 "testPublic")))
+      (should prop)
+      (should-not (phpi-prop-type prop)))
+
+    (let ((prop (phpi-typedef-get-property def1 "testProtected")))
+      (should prop)
+      (should-not (phpi-prop-type prop)))
+
+    (let ((prop (phpi-typedef-get-property def1 "testPrivate")))
+      (should-not prop))
+
+    (phpi-typedef-delete-property def3 (phpinspect-intern-name "testPublic"))
+
+    (let ((prop (phpi-typedef-get-property def1 "testPublic")))
+      (should-not prop))))


Reply via email to