From fa77c385849a25d783961f034215d4ae6bf576d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Mon, 12 Sep 2022 14:41:09 +0200
Subject: [PATCH 01/10] merge fixes ValuList`1

---
 src/Peachpie.Runtime/Collections/ValueList`1.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Peachpie.Runtime/Collections/ValueList`1.cs b/src/Peachpie.Runtime/Collections/ValueList`1.cs
index 5ace26caca..1f38300a4d 100644
--- a/src/Peachpie.Runtime/Collections/ValueList`1.cs
+++ b/src/Peachpie.Runtime/Collections/ValueList`1.cs
@@ -181,6 +181,7 @@ public void Insert(int index, T item)
             }
 
             //
+            _count++;
             _array[index] = item;
         }
 

From 0d12b6b48070c5d1cf4fc4918722f4bc9760e039 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Mon, 12 Sep 2022 14:41:54 +0200
Subject: [PATCH 02/10] merge tests ValueList`1

---
 src/Tests/Peachpie.Runtime.Tests/UtilitiesTests.cs | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/Tests/Peachpie.Runtime.Tests/UtilitiesTests.cs b/src/Tests/Peachpie.Runtime.Tests/UtilitiesTests.cs
index 0ca70b9534..56e57a698a 100644
--- a/src/Tests/Peachpie.Runtime.Tests/UtilitiesTests.cs
+++ b/src/Tests/Peachpie.Runtime.Tests/UtilitiesTests.cs
@@ -35,6 +35,9 @@ public void Bin2HexTest()
         public void ValueListTest()
         {
             var list = new ValueList<int>();
+            
+            Assert.AreEqual(0, list.Count);
+
             for (int i = 0; i < 10; i++)
             {
                 list.AddRange(new[] { 0, 1, 2, 3 });
@@ -42,8 +45,14 @@ public void ValueListTest()
                 list.Insert(list.Count, 5);
             }
 
+            var count2 = list.Count;
+            Assert.AreEqual(10 * (6), count2);
+
             list.Insert(0, -1);
             list.Insert(1, 1);
+
+            var count3 = list.Count;
+            Assert.AreEqual(count2 + 2, count3);
         }
 
         [TestMethod]

From f3cc08464b007d3f658197ca5107a215215277ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Sat, 8 Oct 2022 18:57:47 +0200
Subject: [PATCH 03/10] merge: pdo statement execute fix

---
 src/PDO/Peachpie.Library.PDO/PDOStatement.cs | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/PDO/Peachpie.Library.PDO/PDOStatement.cs b/src/PDO/Peachpie.Library.PDO/PDOStatement.cs
index 58cdb4fee1..4dffe70d77 100644
--- a/src/PDO/Peachpie.Library.PDO/PDOStatement.cs
+++ b/src/PDO/Peachpie.Library.PDO/PDOStatement.cs
@@ -321,11 +321,9 @@ public virtual bool closeCursor()
         /// <returns>Returns TRUE on success or FALSE on failure</returns>
         public virtual bool execute(PhpArray input_parameters = null)
         {
-            if (Result != null)
-            {
-                // reusing command
-                Connection.ClosePendingReader();
-            }
+            // close previous pending reader
+            // https://github.com/peachpiecompiler/peachpie/issues/1069
+            Connection.ClosePendingReader();
 
             // parameters
             BindParameters(_cmd.Parameters, input_parameters);

From 070bbed0cf2f9d97d39ccdb5a6e590dfffe2e681 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Sat, 8 Oct 2022 18:58:43 +0200
Subject: [PATCH 04/10] merge: mysqli_report()

---
 .../MySqlConnectionManager.cs                 |  6 ++
 .../MySqlConnectionResource.cs                | 21 ++++++
 .../MySqli/Constants.cs                       | 30 +++++---
 .../MySqli/Functions.cs                       | 23 ++----
 .../MySqli/MySqliConnectionManager.cs         | 69 ++++++++++++++++++
 src/Peachpie.Library.MySql/MySqli/mysqli.cs   | 11 ++-
 .../Database/ConnectionManager.cs             |  9 +--
 .../Database/ConnectionResource.cs            | 73 ++++++++++++-------
 src/Peachpie.Runtime/Errors.cs                |  6 +-
 9 files changed, 185 insertions(+), 63 deletions(-)
 create mode 100644 src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs

diff --git a/src/Peachpie.Library.MySql/MySqlConnectionManager.cs b/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
index db19ee14f3..3e5b56df43 100644
--- a/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
+++ b/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
@@ -29,5 +29,11 @@ internal MySqlConnectionResource CreateConnection(IDbConnection dbconnection)
 
             return connection;
         }
+
+        public virtual void ReportException(Exception exception, string exceptionMessage)
+        {
+            // MySql outputs the error to php error handler
+            PhpException.Throw(PhpError.Warning, exceptionMessage);
+        }
     }
 }
diff --git a/src/Peachpie.Library.MySql/MySqlConnectionResource.cs b/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
index 429890d7d6..8eb03d9359 100644
--- a/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
+++ b/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
@@ -168,5 +168,26 @@ internal long LastInsertedId
                 return command != null ? MySqlExtensions.LastInsertedId(command) : -1;
             }
         }
+
+        public override int GetLastErrorNumber()
+        {
+            if (LastException == null)
+            {
+                return (int)MySqlErrorCode.None; // success
+            }
+            else if (LastException is MySqlException me)
+            {
+                return (int)me.ErrorCode;
+            }
+            else
+            {
+                return (int)MySqlErrorCode.UnknownError; // unk erro number
+            }
+        }
+
+        protected override void ReportException(Exception exception, string exceptionMessage)
+        {
+            _manager.ReportException(exception, exceptionMessage);
+        }
     }
 }
diff --git a/src/Peachpie.Library.MySql/MySqli/Constants.cs b/src/Peachpie.Library.MySql/MySqli/Constants.cs
index c93e64e7bf..1f1295dcad 100644
--- a/src/Peachpie.Library.MySql/MySqli/Constants.cs
+++ b/src/Peachpie.Library.MySql/MySqli/Constants.cs
@@ -248,20 +248,30 @@ public static class Constants
         /// <summary></summary>
         public const int MYSQLI_SET_CHARSET_NAME = 7;
 
-        //MYSQLI_REPORT_INDEX
-        //Report if no index or bad index was used in a query.
+        /// <summary>
+        /// Turns reporting off.
+        /// </summary>
+        public const int MYSQLI_REPORT_OFF = 0;
 
-        //MYSQLI_REPORT_ERROR
-        //Report errors from mysqli function calls.
+        /// <summary>
+        /// Report errors from mysqli function calls.
+        /// </summary>
+        public const int MYSQLI_REPORT_ERROR = 1;
 
-        //MYSQLI_REPORT_STRICT
-        //Throw a mysqli_sql_exception for errors instead of warnings.
+        /// <summary>
+        /// Throw a <see cref="mysqli_sql_exception"/> for errors instead of warnings.
+        /// </summary>
+        public const int MYSQLI_REPORT_STRICT = 2;
 
-        //MYSQLI_REPORT_ALL
-        //Set all options on (report all).
+        /// <summary>
+        /// Report if no index or bad index was used in a query.
+        /// </summary>
+        public const int MYSQLI_REPORT_INDEX = 4;
 
-        //MYSQLI_REPORT_OFF
-        //Turns reporting off.
+        /// <summary>
+        /// Set all options on (report all).
+        /// </summary>
+        public const int MYSQLI_REPORT_ALL = 0xff;
 
         //MYSQLI_DEBUG_TRACE_ENABLED
         //Is set to 1 if mysqli_debug() functionality is enabled.
diff --git a/src/Peachpie.Library.MySql/MySqli/Functions.cs b/src/Peachpie.Library.MySql/MySqli/Functions.cs
index 9c5527430f..06d0de6b5e 100644
--- a/src/Peachpie.Library.MySql/MySqli/Functions.cs
+++ b/src/Peachpie.Library.MySql/MySqli/Functions.cs
@@ -12,16 +12,6 @@ namespace Peachpie.Library.MySql.MySqli
     [PhpExtension(Constants.ExtensionName)]
     public static class Functions
     {
-        /// <summary>
-        /// Internal object used to store per-request (context) data.
-        /// </summary>
-        internal class MySqliContextData
-        {
-            public static MySqliContextData/*!*/GetContextData(Context ctx) => ctx.GetStatic<MySqliContextData>();
-
-            public string LastConnectionError { get; set; }
-        }
-
         /// <summary>
         /// Initializes MySQLi and returns a resource for use with mysqli_real_connect().
         /// </summary>
@@ -96,7 +86,7 @@ public static mysqli mysqli_connect(Context ctx, IDbConnection dbconnection/*, b
         /// The connection error message. Otherwise <c>null</c>.
         /// </summary>
         public static string mysqli_connect_error(Context ctx, mysqli link = null)
-            => (link != null) ? link.connect_error : MySqliContextData.GetContextData(ctx).LastConnectionError;
+            => (link != null) ? link.connect_error : MySqliConnectionManager.GetInstance(ctx).LastConnectionError;
 
         /// <summary>
         /// Returns the error code from last connect call.
@@ -104,7 +94,7 @@ public static string mysqli_connect_error(Context ctx, mysqli link = null)
         public static int mysqli_connect_errno(Context ctx, mysqli link = null)
             => (link != null)
                 ? link.connect_errno
-                : string.IsNullOrEmpty(MySqliContextData.GetContextData(ctx).LastConnectionError) ? 0 : -1;
+                : string.IsNullOrEmpty(MySqliConnectionManager.GetInstance(ctx).LastConnectionError) ? 0 : -1;
 
         /// <summary>
         /// Returns the error code for the most recent function call.
@@ -369,10 +359,13 @@ public static bool mysqli_ssl_set(mysqli link, string key = null, string cert =
         /// <summary>
         /// Sets mysqli error reporting mode.
         /// </summary>
-        public static bool mysqli_report(int flags)
+        public static bool mysqli_report(Context ctx, ReportMode flags)
         {
-            PhpException.FunctionNotSupported(nameof(mysqli_report));
-            return false;
+            var manager = MySqliConnectionManager.GetInstance(ctx);
+
+            manager.ReportMode = flags;
+
+            return true;
         }
     }
 }
diff --git a/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs b/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs
new file mode 100644
index 0000000000..d71f9b332b
--- /dev/null
+++ b/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MySqlConnector;
+using Pchp.Core;
+
+namespace Peachpie.Library.MySql.MySqli
+{
+    class MySqliConnectionManager : MySqlConnectionManager
+    {
+        /// <summary>
+        /// Gets the manager singleton within runtime context.
+        /// </summary>
+        public static new MySqliConnectionManager GetInstance(Context ctx) => ctx.GetStatic<MySqliConnectionManager>();
+
+        public string LastConnectionError { get; set; }
+
+        /// <summary>
+        /// The error handling mode.
+        /// </summary>
+        public ReportMode ReportMode { get; set; } = ReportMode.Default;
+
+        public override void ReportException(Exception exception, string exceptionMessage)
+        {
+            if (exception == null)
+            {
+                throw new ArgumentNullException(nameof(exception));
+            }
+
+            if ((ReportMode & ReportMode.Error) != 0)
+            {
+                if ((ReportMode & ReportMode.Strict) != 0)
+                {
+                    var errcode = exception is MySqlException me ? me.ErrorCode : MySqlErrorCode.UnknownError;
+
+                    throw new mysqli_sql_exception(exceptionMessage, (int)errcode);
+                }
+                else
+                {
+                    // outputs the error to php error handler
+                    PhpException.Throw(PhpError.Warning, exceptionMessage);
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// MYSQLI_REPORT_*** constants.
+    /// </summary>
+    [PhpHidden, Flags]
+    public enum ReportMode
+    {
+        /// <summary><see cref="Constants.MYSQLI_REPORT_OFF"/></summary>
+        Off = Constants.MYSQLI_REPORT_OFF,
+        /// <summary><see cref="Constants.MYSQLI_REPORT_ERROR"/></summary>
+        Error = Constants.MYSQLI_REPORT_ERROR,
+        /// <summary><see cref="Constants.MYSQLI_REPORT_STRICT"/></summary>
+        Strict = Constants.MYSQLI_REPORT_STRICT,
+        /// <summary><see cref="Constants.MYSQLI_REPORT_INDEX"/></summary>
+        Index = Constants.MYSQLI_REPORT_INDEX,
+        /// <summary><see cref="Constants.MYSQLI_REPORT_ALL"/></summary>
+        All = Constants.MYSQLI_REPORT_ALL,
+
+        /// <summary>Default error handling flags.</summary>
+        Default = Error | Strict, // as it is in PHP 8.1
+    }
+}
diff --git a/src/Peachpie.Library.MySql/MySqli/mysqli.cs b/src/Peachpie.Library.MySql/MySqli/mysqli.cs
index a8ac5eed00..4adad1640b 100644
--- a/src/Peachpie.Library.MySql/MySqli/mysqli.cs
+++ b/src/Peachpie.Library.MySql/MySqli/mysqli.cs
@@ -52,7 +52,7 @@ internal mysqli(Context ctx, IDbConnection dbconnection)
         {
             // create connection resource and
             // register it in the list of active connections
-            this.Connection = MySqlConnectionManager
+            this.Connection = MySqliConnectionManager
                 .GetInstance(ctx)
                 .CreateConnection(dbconnection);
         }
@@ -306,6 +306,7 @@ public PhpValue query(PhpString query, int resultmode = Constants.MYSQLI_STORE_R
         public bool real_connect(Context ctx, string host = null, string username = null, string passwd = null, string dbname = "", int port = -1, string socket = null, int flags = 0)
         {
             var config = ctx.Configuration.Get<MySqlConfiguration>();
+            var manager = MySqliConnectionManager.GetInstance(ctx);
 
             // string $host = ini_get("mysqli.default_host")
             // string $username = ini_get("mysqli.default_user")
@@ -346,12 +347,12 @@ public bool real_connect(Context ctx, string host = null, string username = null
                 }
             }
 
-            Connection = MySqlConnectionManager.GetInstance(ctx)
-                .CreateConnection(connection_string.ToString(), true, -1, out bool success);
+            Connection = manager.CreateConnection(connection_string.ToString(), true, -1, out bool success);
 
             if (success)
             {
                 Connection.Server = host;
+                manager.LastConnectionError = null;
 
                 if (!string.IsNullOrEmpty(dbname))
                 {
@@ -360,9 +361,7 @@ public bool real_connect(Context ctx, string host = null, string username = null
             }
             else
             {
-                MySqliContextData.GetContextData(ctx).LastConnectionError
-                    = connect_error
-                    = Connection.GetLastErrorMessage();
+                manager.LastConnectionError = connect_error = Connection.GetLastErrorMessage();
             }
 
             //
diff --git a/src/Peachpie.Library/Database/ConnectionManager.cs b/src/Peachpie.Library/Database/ConnectionManager.cs
index a7b6e3ae4f..7dceae8a58 100644
--- a/src/Peachpie.Library/Database/ConnectionManager.cs
+++ b/src/Peachpie.Library/Database/ConnectionManager.cs
@@ -16,8 +16,7 @@ public abstract class ConnectionManager<TConnection> : IStaticInit where TConnec
         /// <summary>
         /// Associated runtime context.
         /// </summary>
-        public Context Context => _ctx;
-        Context _ctx;
+        public Context Context { get; private set; }
 
         /// <summary>
         /// List of connections established by the manager.
@@ -69,7 +68,7 @@ public TConnection CreateConnection(string connectionString, bool newConnection,
             if ((success = connection.Connect()) == true)
             {
                 AddConnection(connection);
-                _ctx.RegisterDisposable(connection);
+                this.Context.RegisterDisposable(connection);
             }
 
             return connection;
@@ -108,7 +107,7 @@ protected void AddConnection(TConnection connection)
         public bool RemoveConnection(TConnection connection)
         {
             //
-            _ctx.UnregisterDisposable(connection);
+            this.Context.UnregisterDisposable(connection);
 
             //
             if (_connections.Count != 0)
@@ -132,7 +131,7 @@ public TConnection GetLastConnection()
 
         void IStaticInit.Init(Context ctx)
         {
-            _ctx = ctx;
+            this.Context = ctx;
         }
     }
 }
diff --git a/src/Peachpie.Library/Database/ConnectionResource.cs b/src/Peachpie.Library/Database/ConnectionResource.cs
index 5a17c9a365..cbbcc055f3 100644
--- a/src/Peachpie.Library/Database/ConnectionResource.cs
+++ b/src/Peachpie.Library/Database/ConnectionResource.cs
@@ -29,15 +29,38 @@ public abstract class ConnectionResource : PhpResource
         /// <summary>
 		/// Last result resource.
 		/// </summary>
-		public ResultResource LastResult => _lastResult;
-        private ResultResource _lastResult;
+		public ResultResource LastResult { get; private set; }
+
+        /// <summary>
+        /// Handle exception thrown by the last DB operation.
+        /// </summary>
+        /// <param name="exception">Thrown exception</param>
+        /// <param name="exceptionMessage">Optional formatted message to be reported.</param>
+        private void OnException(Exception exception, string exceptionMessage)
+        {
+            LastException = exception;
+
+            ReportException(exception, exceptionMessage ?? exception.Message);
+        }
+
+        protected virtual void ReportException(Exception exception, string exceptionMessage)
+        {
+            PhpException.Throw(PhpError.Warning, exceptionMessage);
+        }
+
+        /// <summary>
+        /// Handle success by the last DB operation.
+        /// </summary>
+        protected virtual void OnSuccess()
+        {
+            LastException = null;
+        }
 
         /// <summary>
         /// Gets an exception thrown by last performed operation or a <B>null</B> reference 
         /// if that operation succeeded.
         /// </summary>
-        public Exception LastException => _lastException;
-        protected Exception _lastException;
+        public Exception LastException { get; protected set; }
 
         /// <summary>
         /// Gets the number of rows affected by the last query executed on this connection.
@@ -46,10 +69,10 @@ public int LastAffectedRows
         {
             get
             {
-                if (_lastResult == null) return -1;
+                if (LastResult == null) return -1;
 
                 // SELECT gives -1, UPDATE/INSERT gives the number:
-                return (_lastResult.RecordsAffected >= 0) ? _lastResult.RecordsAffected : _lastResult.RowCount;
+                return (LastResult.RecordsAffected >= 0) ? LastResult.RecordsAffected : LastResult.RowCount;
             }
         }
 
@@ -85,12 +108,12 @@ public virtual bool Connect()
             try
             {
                 ActiveConnection.Open();  // TODO: Async
-                _lastException = null;
+
+                OnSuccess();
             }
             catch (Exception e)
             {
-                _lastException = e;
-                PhpException.Throw(PhpError.Warning, LibResources.cannot_open_connection, GetExceptionMessage(e));
+                OnException(e, string.Format(LibResources.cannot_open_connection, GetExceptionMessage(e)));
                 return false;
             }
 
@@ -112,12 +135,11 @@ protected override void FreeManaged()
                     connection.Close();
                 }
 
-                _lastException = null;
+                OnSuccess();
             }
             catch (Exception e)
             {
-                _lastException = e;
-                PhpException.Throw(PhpError.Warning, LibResources.error_closing_connection, GetExceptionMessage(e));
+                OnException(e, string.Format(LibResources.error_closing_connection, GetExceptionMessage(e)));
             }
         }
 
@@ -241,7 +263,7 @@ protected virtual ResultResource ExecuteCommandProtected(IDbCommand command, boo
             {
                 command.Parameters.Clear();
 
-                for (int iparam = 0; iparam < parameters.Count; iparam ++)
+                for (int iparam = 0; iparam < parameters.Count; iparam++)
                 {
                     command.Parameters.Add(parameters[iparam]);
                 }
@@ -261,21 +283,20 @@ protected virtual ResultResource ExecuteCommandProtected(IDbCommand command, boo
                 }
                 else
                 {
-                    _lastResult = null;
+                    LastResult = null;
 
                     // read all data into PhpDbResult:
                     result = GetResult(reader, convertTypes);
                     result.command = command;
 
-                    _lastResult = result;
+                    LastResult = result;
                 }
 
-                _lastException = null;
+                OnSuccess();
             }
             catch (Exception e)
             {
-                _lastException = e;
-                PhpException.Throw(PhpError.Warning, LibResources.command_execution_failed, GetExceptionMessage(e));
+                OnException(e, string.Format(LibResources.command_execution_failed, GetExceptionMessage(e)));
             }
 
             //
@@ -295,11 +316,12 @@ internal void ReexecuteSchemaQuery(ResultResource/*!*/ result)
             try
             {
                 result.Reader = _pendingReader = result.Command.ExecuteReader(CommandBehavior.KeyInfo | CommandBehavior.SchemaOnly);
+
+                OnSuccess();
             }
             catch (Exception e)
             {
-                _lastException = e;
-                PhpException.Throw(PhpError.Warning, LibResources.command_execution_failed, GetExceptionMessage(e));
+                OnException(e, string.Format(LibResources.command_execution_failed, GetExceptionMessage(e)));
             }
         }
 
@@ -318,14 +340,13 @@ public bool SelectDb(string databaseName)
                 if (connection.State == ConnectionState.Open)
                 {
                     connection.ChangeDatabase(databaseName);
-                    _lastException = null;
+                    OnSuccess();
                     return true;
                 }
             }
             catch (Exception e)
             {
-                _lastException = e;
-                PhpException.Throw(PhpError.Warning, LibResources.database_selection_failed, GetExceptionMessage(e));
+                OnException(e, string.Format(LibResources.database_selection_failed, GetExceptionMessage(e)));
             }
 
             return false;
@@ -338,9 +359,11 @@ public bool SelectDb(string databaseName)
         /// <param name="e">Exception.</param>
         /// <returns>The message.</returns>
         /// <exception cref="ArgumentNullException"><paramref name="e"/> is a <B>null</B> reference.</exception>
-        public virtual string GetExceptionMessage(System.Exception/*!*/ e)
+        public virtual string GetExceptionMessage(Exception/*!*/ e)
         {
-            if (e == null) throw new ArgumentNullException(nameof(e));
+            if (e == null)
+                throw new ArgumentNullException(nameof(e));
+
             return PhpException.ToErrorMessage(e.Message);
         }
 
diff --git a/src/Peachpie.Runtime/Errors.cs b/src/Peachpie.Runtime/Errors.cs
index ce4c3e6eb1..acb90095f6 100644
--- a/src/Peachpie.Runtime/Errors.cs
+++ b/src/Peachpie.Runtime/Errors.cs
@@ -494,8 +494,10 @@ public static void ObjectConvertError(string classname, string targettype)
         /// <exception cref="ArgumentNullException"><paramref name="exceptionMessage"/> is a <B>null</B> reference.</exception>
         public static string ToErrorMessage(string exceptionMessage)
         {
-            if (exceptionMessage == null) throw new ArgumentNullException("exceptionMessage");
-            return exceptionMessage.TrimEnd(new char[] { '.' });
+            if (exceptionMessage == null)
+                throw new ArgumentNullException(nameof(exceptionMessage));
+
+            return exceptionMessage.AsSpan().TrimEnd('.').ToString();
         }
 
         internal static void ThrowSelfOutOfClass()

From d88bbf1f58facfb6110023c5026ad1429612b154 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Tue, 13 Sep 2022 17:06:11 +0200
Subject: [PATCH 05/10] GetFileAttributes() helper

---
 src/Peachpie.Runtime/Utilities/Platform.cs | 67 ++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/src/Peachpie.Runtime/Utilities/Platform.cs b/src/Peachpie.Runtime/Utilities/Platform.cs
index cd52359c98..20c8b5ec55 100644
--- a/src/Peachpie.Runtime/Utilities/Platform.cs
+++ b/src/Peachpie.Runtime/Utilities/Platform.cs
@@ -2,7 +2,9 @@
 
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Runtime.InteropServices;
+using System.Security;
 using System.Text;
 
 namespace Pchp.Core.Utilities
@@ -71,6 +73,20 @@ static CurrentPlatform()
         /// Replaces <see cref="AltDirectorySeparator"/> to <see cref="DirectorySeparator"/>.
         /// </summary>
         public static string NormalizeSlashes(string path) => path.Replace(AltDirectorySeparator, DirectorySeparator);
+
+        public static bool GetFileAttributes(string name, out bool isfile, out bool isdir)
+        {
+            if (IsWindows)
+            {
+                return WindowsPlatform.GetFileAttributes(name, out isfile, out isdir);
+            }
+            else
+            {
+                isfile = File.Exists(name);
+                isdir = !isfile && Directory.Exists(name);
+                return isfile || isdir;
+            }
+        }
     }
 
     #endregion
@@ -183,6 +199,57 @@ internal static void GetVersionInformation(
                 suitemask = osVersionInfo.wSuiteMask;
             }
         }
+
+        [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
+        static extern bool GetFileAttributesEx(string name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
+
+        [Serializable]
+        struct WIN32_FILE_ATTRIBUTE_DATA
+        {
+            internal int fileAttributes;
+
+            internal FILE_TIME ftCreationTime;
+
+            internal FILE_TIME ftLastAccessTime;
+
+            internal FILE_TIME ftLastWriteTime;
+
+            internal int fileSizeHigh;
+
+            internal int fileSizeLow;
+        }
+
+        struct FILE_TIME
+        {
+            uint ftTimeLow;
+
+            uint ftTimeHigh;
+
+            public FILE_TIME(long fileTime)
+            {
+                ftTimeLow = (uint)fileTime;
+                ftTimeHigh = (uint)(fileTime >> 32);
+            }
+
+            public long ToTicks() => (long)(((ulong)ftTimeHigh << 32) + ftTimeLow);
+        }
+
+        public static bool GetFileAttributes(string name, out bool isfile, out bool isdir)
+        {
+            var data = default(WIN32_FILE_ATTRIBUTE_DATA);
+
+            if (GetFileAttributesEx(name, 0, ref data))
+            {
+                isfile = (data.fileAttributes & (int)FileAttributes.Directory) == 0;
+                isdir = (data.fileAttributes & (int)FileAttributes.Directory) != 0;
+                return true;
+            }
+            else
+            {
+                isfile = isdir = false;
+                return false;
+            }
+        }
     }
 
     #endregion

From c6f92d76630c459b6f8b308b27f39c18c976bd5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Tue, 13 Sep 2022 00:25:26 +0200
Subject: [PATCH 06/10] Attribute:: constants and missing annotations

---
 src/Peachpie.Runtime/std/Attribute.cs | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/src/Peachpie.Runtime/std/Attribute.cs b/src/Peachpie.Runtime/std/Attribute.cs
index a7d8301026..921512a606 100644
--- a/src/Peachpie.Runtime/std/Attribute.cs
+++ b/src/Peachpie.Runtime/std/Attribute.cs
@@ -9,7 +9,29 @@ namespace Pchp.Core.Std
     /// </summary>
     [PhpType(PhpTypeAttribute.InheritName, MinimumLangVersion = "8.0"), PhpExtension("Core")]
     [AttributeUsage(AttributeTargets.Class)]
+    [Attribute(Attribute.TARGET_CLASS)]
     public sealed class Attribute : System.Attribute
     {
+        public const int TARGET_CLASS = 1;
+        public const int TARGET_FUNCTION = 2;
+        public const int TARGET_METHOD = 4;
+        public const int TARGET_PROPERTY = 8;
+        public const int TARGET_CLASS_CONSTANT = 16;
+        public const int TARGET_PARAMETER = 32;
+        public const int TARGET_ALL = TARGET_CLASS|TARGET_FUNCTION|TARGET_METHOD|TARGET_PROPERTY|TARGET_CLASS_CONSTANT|TARGET_PARAMETER;
+
+        public const int IS_REPEATABLE = 64;
+
+        public int flags;
+
+        public Attribute(int flags = TARGET_ALL)
+        {
+            __construct(flags);
+        }
+
+        public void __construct(int flags = TARGET_ALL)
+        {
+            this.flags = flags;
+        }
     }
 }

From f9d9cff7c3e5ebfed76ae3ef4c0acec81abe803a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Thu, 13 Oct 2022 22:42:26 +0200
Subject: [PATCH 07/10] typo

(cherry picked from commit 8f8b90122241722fb32dad75ee21668b08cc2827)
---
 src/Peachpie.AspNetCore.Mvc/HttpContextExtension.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Peachpie.AspNetCore.Mvc/HttpContextExtension.cs b/src/Peachpie.AspNetCore.Mvc/HttpContextExtension.cs
index 28d05b83e3..eb8feddb13 100644
--- a/src/Peachpie.AspNetCore.Mvc/HttpContextExtension.cs
+++ b/src/Peachpie.AspNetCore.Mvc/HttpContextExtension.cs
@@ -54,7 +54,7 @@ public CustomViewDataDictionary(ViewDataDictionary source, object model, Type mo
         /// <remarks>
         /// Can be used within a PHP code.
         /// Note current <see cref="HttpContext.RequestServices"/> must provide <see cref="ICompositeViewEngine"/>
-        /// and <see cref="ITempDataDictionaryFactory"/>. These services are susually added by <c>Mvc</c> services.
+        /// and <see cref="ITempDataDictionaryFactory"/>. These services are usually added by <c>Mvc</c> services.
         /// </remarks>
         public static void RenderPartial(this Context phpContext, string viewName, object model = null)
         {

From 9d72b9e7e6c9b5a36cf811f52689139a5cc77f93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Fri, 14 Oct 2022 14:53:30 +0200
Subject: [PATCH 08/10] Peachpie.App no snupkg

(cherry picked from commit 26ec71b52b9be88a8c846134ccf52c28939f39f5)
---
 src/Peachpie.App/Peachpie.App.csproj | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Peachpie.App/Peachpie.App.csproj b/src/Peachpie.App/Peachpie.App.csproj
index 140b07e9d7..8835108f9d 100644
--- a/src/Peachpie.App/Peachpie.App.csproj
+++ b/src/Peachpie.App/Peachpie.App.csproj
@@ -6,6 +6,7 @@
     <AssemblyName>Peachpie.App</AssemblyName>
     <PackageId>Peachpie.App</PackageId>
     <IncludeBuildOutput>false</IncludeBuildOutput>
+    <IncludeSymbols>false</IncludeSymbols>
     <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
     <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
     <NoWarn>$(NoWarn),NU5128</NoWarn>

From b5dad207e406c68bc15ae00806f900b6d0b1c880 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Wed, 16 Nov 2022 21:41:44 +0100
Subject: [PATCH 09/10] merge: header, PhpStackTrace

---
 src/Peachpie.Library/Web.cs                      | 16 +++++++++++++++-
 src/Peachpie.Runtime/Reflection/PhpStackTrace.cs |  3 ++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/Peachpie.Library/Web.cs b/src/Peachpie.Library/Web.cs
index 0466eaf92a..f5e207db9c 100644
--- a/src/Peachpie.Library/Web.cs
+++ b/src/Peachpie.Library/Web.cs
@@ -514,9 +514,23 @@ static bool TryMatchHttpStatusHeader(ReadOnlySpan<char> header, out int code)
 		/// </remarks>
 		public static void header(Context ctx, string str, bool replace = true, int http_response_code = 0)
         {
+            if (string.IsNullOrEmpty(str))
+            {
+                // empty arg, ignore
+                //PhpException.InvalidArgument(nameof(str));
+                return;
+            }
+
             var webctx = ctx.HttpPhpContext;
-            if (webctx == null || string.IsNullOrEmpty(str) || webctx.HeadersSent)
+            if (webctx == null)
+            {
+                // ignore on console app
+                return;
+            }
+
+            if (webctx.HeadersSent)
             {
+                // header sent
                 PhpException.Throw(PhpError.Notice, Resources.Resources.headers_has_been_sent);
                 return;
             }
diff --git a/src/Peachpie.Runtime/Reflection/PhpStackTrace.cs b/src/Peachpie.Runtime/Reflection/PhpStackTrace.cs
index 2f27873c6c..e51850f4ed 100644
--- a/src/Peachpie.Runtime/Reflection/PhpStackTrace.cs
+++ b/src/Peachpie.Runtime/Reflection/PhpStackTrace.cs
@@ -341,7 +341,8 @@ public string TypeName
 
                     if (tinfo.IsPublic && tinfo.IsAbstract) // => public static
                     {
-                        if (tinfo.Assembly.IsDefined(typeof(PhpExtensionAttribute)))
+                        var extensionAttrs = Attribute.GetCustomAttributes(tinfo.Assembly, typeof(PhpExtensionAttribute));
+                        if (extensionAttrs != null && extensionAttrs.Length != 0)
                         {
                             // library function
                             return null;

From e6b88ecbf87eb7957652322b2c9b54d88a4b4937 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= <jmisek@gmail.com>
Date: Sat, 21 Jan 2023 17:03:04 +0100
Subject: [PATCH 10/10] merge from main

updated parser
updated phar parser
droped phpdoc type hint support
fixed match, fixed post increment
---
 Directory.Build.props                         |   3 +-
 .../CodeGen/Graph/BoundExpression.cs          |   5 +-
 .../CommandLine/PhpCommandLineParser.cs       |  19 --
 .../Compilation/PhpCompilationOptions.cs      |  39 ---
 .../DocumentationCommentCompiler.cs           |  39 ++-
 .../FlowAnalysis/PHPDoc.cs                    | 240 +++++++-----------
 .../FlowAnalysis/Passes/DiagnosticWalker.cs   |  28 --
 .../FlowAnalysis/TypeRef/TypeRefContext.cs    |   2 +-
 .../Peachpie.CodeAnalysis.csproj              |   4 +-
 src/Peachpie.CodeAnalysis/PhpResources.resx   |   5 -
 .../Semantics/SemanticsBinder.cs              |   2 +-
 .../Symbols/Php/PhpRoutineSymbolExtensions.cs |  21 +-
 .../Symbols/Source/SourceFieldSymbol.cs       |  37 +--
 .../Symbols/Source/SourceFunctionSymbol.cs    |   3 +-
 .../Source/SourceGlobalMethodSymbol.cs        |   5 +-
 .../Symbols/Source/SourceLambdaSymbol.cs      |   7 +-
 .../Symbols/Source/SourceMethodSymbol.cs      |   3 +-
 .../Symbols/Source/SourceParameterSymbol.cs   |  21 +-
 .../Symbols/Source/SourceRoutineSymbol.cs     |  27 +-
 .../Symbols/Source/SourceSymbolCollection.cs  |   2 +-
 .../Symbols/WellKnownMembersHelper.cs         |   6 +-
 .../Syntax/AdditionalSyntaxProvider.cs        |   3 +-
 .../Syntax/PhpSourceUnit.cs                   |   2 +-
 .../Syntax/PhpTokenProvider.cs                |   5 +-
 .../Utilities/AstUtils.cs                     |  30 ++-
 25 files changed, 193 insertions(+), 365 deletions(-)

diff --git a/Directory.Build.props b/Directory.Build.props
index 999258673f..04b64d73ce 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -40,7 +40,8 @@
     
     <PeachpieLibraryRegularExpressionsVersion>1.6.0</PeachpieLibraryRegularExpressionsVersion>
     <MySqlConnectorVersion>2.0.0</MySqlConnectorVersion>
-    <ParserVersion>8.1.8808</ParserVersion>
+    <ParserVersion>8.2.12169</ParserVersion>
+    <PeachpieMicrosoftCodeAnalysisVersion>3.7.1</PeachpieMicrosoftCodeAnalysisVersion>
     
   </PropertyGroup>
 
diff --git a/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs b/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs
index 2cc4544430..bb395f1a7e 100644
--- a/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs
+++ b/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs
@@ -3958,8 +3958,9 @@ internal override TypeSymbol Emit(CodeGenerator cg)
             {
                 // store value of target
                 // <temp> = TARGET
-                tempvar = cg.GetTemporaryLocal(target_load_type);
                 cg.EmitOpCode(ILOpCode.Dup);
+                var tempvar_type = cg.EmitDereference(target_load_type);
+                tempvar = cg.GetTemporaryLocal(tempvar_type);
                 cg.Builder.EmitLocalStore(tempvar);
             }
 
@@ -3977,8 +3978,8 @@ internal override TypeSymbol Emit(CodeGenerator cg)
             {
                 // store value of result
                 // <temp> = TARGET
-                tempvar = cg.GetTemporaryLocal(op_type);
                 cg.EmitOpCode(ILOpCode.Dup);
+                tempvar = cg.GetTemporaryLocal(op_type);
                 cg.Builder.EmitLocalStore(tempvar);
             }
 
diff --git a/src/Peachpie.CodeAnalysis/CommandLine/PhpCommandLineParser.cs b/src/Peachpie.CodeAnalysis/CommandLine/PhpCommandLineParser.cs
index 74acbaa6b2..3274d2d4af 100644
--- a/src/Peachpie.CodeAnalysis/CommandLine/PhpCommandLineParser.cs
+++ b/src/Peachpie.CodeAnalysis/CommandLine/PhpCommandLineParser.cs
@@ -180,7 +180,6 @@ internal override CommandLineArguments CommonParse(IEnumerable<string> args, str
             PhpOptimizationLevel optimization = PhpOptimizationLevel.Debug;
             bool concurrentBuild = true;
             var diagnosticOptions = new Dictionary<string, ReportDiagnostic>();
-            PhpDocTypes phpdocTypes = PhpDocTypes.None;
             OutputKind outputKind = OutputKind.ConsoleApplication;
             bool optionsEnded = false;
             bool displayHelp = false, displayLogo = true;
@@ -584,23 +583,6 @@ internal override CommandLineArguments CommonParse(IEnumerable<string> args, str
                         documentationPath = value ?? string.Empty;
                         break;
 
-                    case "phpdoctypes+":
-                        phpdocTypes = PhpDocTypes.All;
-                        break;
-                    case "phpdoctypes-":
-                        phpdocTypes = PhpDocTypes.None;
-                        break;
-                    case "phpdoctypes":
-                        if (value == null)
-                        {
-                            phpdocTypes = PhpDocTypes.All;
-                        }
-                        else
-                        {
-                            phpdocTypes = (PhpDocTypes)Enum.Parse(typeof(PhpDocTypes), value);
-                        }
-                        break;
-
                     case "modulename":
                         var unquotedModuleName = RemoveQuotesAndSlashes(value);
                         if (string.IsNullOrEmpty(unquotedModuleName))
@@ -835,7 +817,6 @@ internal override CommandLineArguments CommonParse(IEnumerable<string> args, str
                 mainTypeName: mainTypeName,
                 scriptClassName: WellKnownMemberNames.DefaultScriptClassName,
                 versionString: versionString,
-                phpdocTypes: phpdocTypes,
                 parseOptions: parseOptions,
                 defines: defines.ToImmutableDictionary(),
                 diagnostics: diagnostics.AsImmutable(),
diff --git a/src/Peachpie.CodeAnalysis/Compilation/PhpCompilationOptions.cs b/src/Peachpie.CodeAnalysis/Compilation/PhpCompilationOptions.cs
index dc65f9d2d6..fa1c910c2e 100644
--- a/src/Peachpie.CodeAnalysis/Compilation/PhpCompilationOptions.cs
+++ b/src/Peachpie.CodeAnalysis/Compilation/PhpCompilationOptions.cs
@@ -10,35 +10,6 @@
 
 namespace Pchp.CodeAnalysis
 {
-    /// <summary>
-    /// Options for getting type information from correspodning PHPDoc comments.
-    /// </summary>
-    [Flags]
-    public enum PhpDocTypes
-    {
-        None = 0,
-
-        /// <summary>
-        /// Fields type will be declared according to PHPDoc @var tag.
-        /// </summary>
-        FieldTypes = 1,
-
-        /// <summary>
-        /// Parameter type will be declared according to PHPDoc @param tag.
-        /// </summary>
-        ParameterTypes = 2,
-
-        /// <summary>
-        /// Method return type will be declared according to PHPDoc @return tag.
-        /// </summary>
-        ReturnTypes = 4,
-
-        /// <summary>
-        /// Declare all additional type information from PHPDoc.
-        /// </summary>
-        All = FieldTypes | ParameterTypes | ReturnTypes,
-    }
-
     /// <summary>
     /// Represents various options that affect compilation, such as 
     /// whether to emit an executable or a library, whether to optimize
@@ -70,11 +41,6 @@ public sealed class PhpCompilationOptions : CompilationOptions, IEquatable<PhpCo
         /// </summary>
         public string TargetFramework { get; private set; }
 
-        /// <summary>
-        /// Options for getting type information from correspodning PHPDoc comments.
-        /// </summary>
-        public PhpDocTypes PhpDocTypes { get; private set; }
-
         /// <summary>
         /// Whether to generate an embedded resource containing additional information about the source symbols.
         /// Used by runtime for reflection.
@@ -178,7 +144,6 @@ public PhpCompilationOptions(
             AssemblyIdentityComparer assemblyIdentityComparer = null,
             StrongNameProvider strongNameProvider = null,
             bool publicSign = false,
-            PhpDocTypes phpdocTypes = PhpDocTypes.None,
             bool embedSourceMetadata = true,
             ImmutableArray<Diagnostic> diagnostics = default,
             PhpParseOptions parseOptions = null,
@@ -201,7 +166,6 @@ public PhpCompilationOptions(
                    strongNameProvider: strongNameProvider,
                    metadataImportOptions: MetadataImportOptions.Public,
                    publicSign: publicSign,
-                   phpdocTypes: phpdocTypes,
                    embedSourceMetadata: embedSourceMetadata,
                    diagnostics: diagnostics,
                    defines: defines,
@@ -244,7 +208,6 @@ internal PhpCompilationOptions(
             StrongNameProvider strongNameProvider,
             MetadataImportOptions metadataImportOptions,
             bool publicSign,
-            PhpDocTypes phpdocTypes,
             bool embedSourceMetadata,
             ImmutableArray<Diagnostic> diagnostics,
             PhpParseOptions parseOptions,
@@ -262,7 +225,6 @@ internal PhpCompilationOptions(
             this.SdkDirectory = sdkDirectory;
             this.SubDirectory = subDirectory;
             this.TargetFramework = targetFramework;
-            this.PhpDocTypes = phpdocTypes;
             this.EmbedSourceMetadata = embedSourceMetadata;
             this.ParseOptions = parseOptions;
             this.Diagnostics = diagnostics;
@@ -304,7 +266,6 @@ private PhpCompilationOptions(PhpCompilationOptions other) : this(
             metadataImportOptions: other.MetadataImportOptions,
             reportSuppressedDiagnostics: other.ReportSuppressedDiagnostics,
             publicSign: other.PublicSign,
-            phpdocTypes: other.PhpDocTypes,
             embedSourceMetadata: other.EmbedSourceMetadata,
             diagnostics: other.Diagnostics,
             parseOptions: other.ParseOptions,
diff --git a/src/Peachpie.CodeAnalysis/DocumentationComments/DocumentationCommentCompiler.cs b/src/Peachpie.CodeAnalysis/DocumentationComments/DocumentationCommentCompiler.cs
index 9766d29d1f..56947a699d 100644
--- a/src/Peachpie.CodeAnalysis/DocumentationComments/DocumentationCommentCompiler.cs
+++ b/src/Peachpie.CodeAnalysis/DocumentationComments/DocumentationCommentCompiler.cs
@@ -9,6 +9,8 @@
 using System.Text;
 using Devsense.PHP.Syntax;
 using System.Diagnostics;
+using Pchp.CodeAnalysis.FlowAnalysis;
+using Devsense.PHP.Syntax.Ast;
 
 namespace Pchp.CodeAnalysis.DocumentationComments
 {
@@ -151,7 +153,7 @@ static void WriteParam(TextWriter output, string pname, string pdesc, string typ
             output.WriteLine("</param>");
         }
 
-        static void WriteException(TextWriter output, string[] types, string pdesc)
+        static void WriteException(TextWriter output, string types, string pdesc)
         {
             if (string.IsNullOrWhiteSpace(pdesc) || types == null)
             {
@@ -159,10 +161,8 @@ static void WriteException(TextWriter output, string[] types, string pdesc)
             }
 
             //
-            for (int i = 0; i < types.Length; i++)
+            foreach (var t in types.Split('|'))
             {
-                var t = types[i];
-
                 output.Write("<exception cref=\"");
                 output.Write(XmlEncode(QualifiedName.Parse(t, true).ClrName()));   // TODO: CommentIdResolver.GetId(..)    // TODO: translate correctly using current naming context
                 output.Write('\"');
@@ -188,27 +188,23 @@ public static void WriteRoutine(TextWriter output, SourceRoutineSymbol routine)
             // PHPDoc
             WriteSummary(output, phpdoc.Summary);
 
-            var elements = phpdoc.Elements;
-            for (int i = 0; i < elements.Length; i++)
+            for (var entry = phpdoc.Entries; entry != null; entry = entry.Next)
             {
                 // user parameters
-                if (elements[i] is PHPDocBlock.ParamTag p)
+                if (entry.IsDocTag("@param") && entry.GetEntryText(out _, out var typename, out var varname, out var desc))
                 {
-                    if (p.VariableName != null)
+                    if (varname != null)
                     {
-                        WriteParam(output, p.VariableName.TrimStart('$'), p.Description, p.TypeNames);
+                        WriteParam(output, varname.TrimStart('$'), desc, typename);
                     }
                 }
-                else if (elements[i] is PHPDocBlock.ReturnTag rtag)
+                else if (entry.IsDocTag("@return") && entry.GetEntryText(out _, out var tname, out _, out desc))
                 {
-                    if (!string.IsNullOrWhiteSpace(rtag.Description))
-                    {
-                        output.WriteLine("<returns>{0}</returns>", XmlEncode(rtag.Description));
-                    }
+                    output.WriteLine("<returns>{0}</returns>", XmlEncode(desc));
                 }
-                else if (elements[i] is PHPDocBlock.ExceptionTag ex)
+                else if (entry.IsDocTag("@exception") && entry.GetEntryText(out _, out typename, out _, out desc))
                 {
-                    WriteException(output, ex.TypeNamesArray, ex.Description);
+                    WriteException(output, typename, desc);
                 }
             }
 
@@ -284,13 +280,16 @@ void WriteType(SourceTypeSymbol type)
                     {
                         // try @var or @staticvar:
                         var vartag = field.FindPhpDocVarTag();
-                        if (vartag != null)
+                        if (vartag != null && vartag.GetEntryText(out _, out var typename, out var varname, out var summary2))
                         {
-                            summary = vartag.Description;
+                            if (!string.IsNullOrEmpty(summary2))
+                            {
+                                summary = summary2;
+                            }
 
-                            if (!string.IsNullOrEmpty(vartag.TypeNames))
+                            if (!string.IsNullOrEmpty(typename))
                             {
-                                value = string.Format("<value>{0}</value>", XmlEncode(vartag.TypeNames));
+                                value = string.Format("<value>{0}</value>", XmlEncode(typename));
                             }
                         }
                     }
diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/PHPDoc.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/PHPDoc.cs
index b02022b0cc..41af4c9c28 100644
--- a/src/Peachpie.CodeAnalysis/FlowAnalysis/PHPDoc.cs
+++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/PHPDoc.cs
@@ -1,4 +1,6 @@
-using Devsense.PHP.Syntax;
+using Devsense.PHP.Ast.DocBlock;
+using Devsense.PHP.Syntax;
+using Pchp.CodeAnalysis.Emit;
 using Pchp.CodeAnalysis.Semantics;
 using Pchp.CodeAnalysis.Symbols;
 using System;
@@ -6,6 +8,7 @@
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using static System.Net.Mime.MediaTypeNames;
 
 namespace Pchp.CodeAnalysis.FlowAnalysis
 {
@@ -14,196 +17,141 @@ namespace Pchp.CodeAnalysis.FlowAnalysis
     /// </summary>
     internal static class PHPDoc
     {
-        private delegate TypeRefMask TypeMaskGetter(TypeRefContext ctx);
-
-        /// <summary>
-        /// Well-known PHP type names used in PHPDoc.
-        /// </summary>
-        private static readonly Dictionary<string, TypeMaskGetter>/*!*/_knownTypes = new Dictionary<string, TypeMaskGetter>(StringComparer.OrdinalIgnoreCase)
+        public static IDocEntry GetDocEntry(this IDocBlock phpdoc, string tag)
         {
-            { "int", ctx => ctx.GetLongTypeMask()},
-            { "integer", ctx => ctx.GetLongTypeMask()},
-            { "long", ctx => ctx.GetLongTypeMask()},
-            { "number", ctx => ctx.GetNumberTypeMask()},
-            { "numeric", ctx => ctx.GetNumberTypeMask()},
-            { "string", ctx => ctx.GetStringTypeMask()},
-            { "bool", ctx => ctx.GetBooleanTypeMask()},
-            { "boolean", ctx => ctx.GetBooleanTypeMask()},
-            { "false", ctx => ctx.GetBooleanTypeMask()},
-            { "true", ctx => ctx.GetBooleanTypeMask()},
-            { "float", ctx => ctx.GetDoubleTypeMask()},
-            { "double", ctx => ctx.GetDoubleTypeMask()},
-            { "array", ctx => ctx.GetArrayTypeMask()},
-            { "resource", ctx => ctx.GetResourceTypeMask()},
-            { "null", ctx => ctx.GetNullTypeMask()},
-            { "object", ctx => ctx.GetSystemObjectTypeMask()},
-            { "void", ctx => default(TypeRefMask).WithSubclasses},  // avoid being 0 (which means uninitialized)
-            //{ "nothing", ctx => 0},
-            { "callable", ctx => ctx.GetCallableTypeMask()},
-            { "mixed", ctx => TypeRefMask.AnyType},
-        };
+            if (phpdoc != null)
+            {
+                for (var entry = phpdoc.Entries; entry != null; entry = entry.Next)
+                {
+                    if (IsDocTag(entry, tag))
+                    {
+                        return entry;
+                    }
+                }
+            }
 
-        /// <summary>
-        /// Gets value indicating whether given parameter represents known PHPDoc type name.
-        /// </summary>
-        public static bool IsKnownType(string tname)
-        {
-            return !string.IsNullOrEmpty(tname) && _knownTypes.ContainsKey(tname);
+            return null;
         }
 
-        /// <summary>
-        /// Gets type mask of known PHPDoc type name or <c>0</c> if such type is now known.
-        /// </summary>
-        public static TypeRefMask GetKnownTypeMask(TypeRefContext/*!*/typeCtx, string tname)
+        public static bool IsDocTag(this IDocEntry entry, string tag)
         {
-            Contract.ThrowIfNull(typeCtx);
-            if (!string.IsNullOrEmpty(tname))
+            if (entry != null)
             {
-                TypeMaskGetter getter;
-                if (_knownTypes.TryGetValue(tname, out getter))
+                var str = entry.ToString();
+                if (str.StartsWith(tag, StringComparison.Ordinal))
                 {
-                    return getter(typeCtx);
+                    var idx = tag.Length;
+                    if (str.Length == idx || char.IsWhiteSpace(str[idx]))
+                    {
+                        return true;
+                    }
                 }
             }
 
-            return 0;   // void
+            return false;
         }
 
         /// <summary>
-        /// Gets type mask representing given type name.
+        /// Gets parameter type from given PHPDoc block.
         /// </summary>
-        public static TypeRefMask GetTypeMask(TypeRefContext/*!*/typeCtx, string tname, NamingContext naming, bool fullyQualified = false)
+        public static IDocEntry GetParamTag(PHPDocBlock phpdoc, int paramIndex, string paramName)
         {
-            if (!string.IsNullOrEmpty(tname))
+            if (phpdoc != null)
             {
-                // handle various array conventions
-                if (tname.LastCharacter() == ']')
-                {
-                    // "TName[]"
-                    if (tname.EndsWith("[]", StringComparison.Ordinal))
-                    {
-                        var elementType = GetTypeMask(typeCtx, tname.Remove(tname.Length - 2), naming, fullyQualified);
-                        return typeCtx.GetArrayTypeMask(elementType);
-                    }
-
-                    // "array[TName]"
-                    var arrayTypeName = QualifiedName.Array.Name.Value;
-                    if (tname.Length > arrayTypeName.Length && tname[arrayTypeName.Length] == '[' &&
-                        tname.StartsWith(arrayTypeName, StringComparison.OrdinalIgnoreCase))
-                    {
-                        var elementTypeName = tname.Substring(arrayTypeName.Length + 1, tname.Length - arrayTypeName.Length - 2);
-                        var elementType = GetTypeMask(typeCtx, elementTypeName, naming, fullyQualified);
-                        return typeCtx.GetArrayTypeMask(elementType);
-                    }
-
-                    // unknown something // ...                    
-                }
-                else if (tname[0] == '&')
-                {
-                    return GetTypeMask(typeCtx, tname.Substring(1), naming, fullyQualified).WithRefFlag;
-                }
-                else
+                int pi = 0;
+                for (var entry = phpdoc.Entries; entry != null; entry = entry.Next)
                 {
-                    var result = GetKnownTypeMask(typeCtx, tname);
-                    if (result.IsUninitialized)
+                    if (IsDocTag(entry, "@param"))
                     {
-                        var qname = NameUtils.MakeQualifiedName(tname, false);
-                        if (!fullyQualified && naming != null && !qname.IsReservedClassName)
-                            qname = QualifiedName.TranslateAlias(qname, AliasKind.Type, naming.Aliases, naming.CurrentNamespace);
-
-                        if (qname.IsPrimitiveTypeName)
+                        if (entry.ToString().IndexOf(paramName) >= 0)
                         {
-                            result = GetKnownTypeMask(typeCtx, qname.Name.Value);
-                            if (!result.IsUninitialized)
-                                return result;
+                            return entry;
                         }
 
-                        if (qname.IsSelfClassName)
-                        {
-                            return typeCtx.GetSelfTypeMask();
-                        }
-
-                        result = BoundTypeRefFactory.Create(qname, typeCtx.SelfType as SourceTypeSymbol).GetTypeRefMask(typeCtx);
+                        //
+                        pi++;
                     }
-
-                    //Contract.Assert(!result.IsUninitialized);
-                    return result;
                 }
             }
 
-            return 0;
+            return null;
         }
 
-        /// <summary>
-        /// Gets type mask representing given type name.
-        /// </summary>
-        public static TypeRefMask GetTypeMask(TypeRefContext/*!*/typeCtx, string[] tnames, NamingContext naming, bool fullyQualified = false)
+        static ReadOnlySpan<char> SliceWord(ReadOnlySpan<char> text)
         {
-            TypeRefMask result = 0;
-
-            foreach (var tname in tnames)
-                result |= GetTypeMask(typeCtx, tname, naming, fullyQualified);
+            var i = 0;
+            while (i < text.Length && char.IsWhiteSpace(text[i])) i++;
+            while (i < text.Length && !char.IsWhiteSpace(text[i])) i++;
 
-            return result;
+            return text.Slice(0, i);
         }
 
-        /// <summary>
-        /// Gets type mask at target ctype context representing given type names from given routine.
-        /// </summary>
-        public static TypeRefMask GetTypeMask(TypeRefContext/*!*/targetCtx, Symbols.SourceRoutineSymbol/*!*/routine, string[] tnames, bool fullyQualified = false)
+        public static bool GetEntryText(this IDocEntry entry, out string text)
         {
-            Contract.ThrowIfNull(targetCtx);
-            Contract.ThrowIfNull(routine);
+            if (entry != null)
+            {
+                var str = entry.ToString().AsSpan().Trim();
 
-            return GetTypeMask(targetCtx, routine.TypeRefContext, tnames, routine.GetNamingContext(), fullyQualified);
-        }
+                // @tagname [text]
+                int i;
 
-        /// <summary>
-        /// Gets type mask at target type context representing given type names from given routine.
-        /// </summary>
-        public static TypeRefMask GetTypeMask(TypeRefContext/*!*/targetCtx, TypeRefContext/*!*/ctx, string[] tnames, NamingContext naming, bool fullyQualified = false)
-        {
-            Contract.ThrowIfNull(targetCtx);
-            Contract.ThrowIfNull(ctx);
+                // @tagname
+                if (str.Length > 0 && str[0] == '@')
+                {
+                    var tagname = SliceWord(str);
+                    str = str.Slice(tagname.Length).TrimStart();
+                }
 
-            var mask = GetTypeMask(ctx, tnames, naming, fullyQualified);
-            return targetCtx.AddToContext(ctx, mask);
+                // 
+                text = str.ToString();
+                return true;
+            }
+
+            text = null;
+            return false;
         }
 
-        /// <summary>
-        /// Gets parameter type from given PHPDoc block.
-        /// </summary>
-        public static PHPDocBlock.ParamTag GetParamTag(PHPDocBlock phpdoc, int paramIndex, string paramName)
+        public static bool GetEntryText(this IDocEntry entry,
+            out string tagname,
+            out string typename,
+            out string varname,
+            out string description)
         {
-            PHPDocBlock.ParamTag result = null;
+            tagname = typename = varname = description = null;
 
-            if (phpdoc != null)
+            if (entry != null)
             {
-                int pi = 0;
-                var elements = phpdoc.Elements;
-                foreach (var element in elements)
+                var str = entry.ToString().AsSpan().Trim();
+
+                // @tagname [typename] [$varname] [description]
+                
+                // @tagname
+                if (str.Length > 0 && str[0] == '@')
                 {
-                    var ptag = element as PHPDocBlock.ParamTag;
-                    if (ptag != null)
-                    {
-                        if (string.IsNullOrEmpty(ptag.VariableName))
-                        {
-                            if (pi == paramIndex)
-                                result = ptag;  // found @param by index
-                        }
-                        else if (string.Equals(ptag.VariableName.Substring(1), paramName, StringComparison.OrdinalIgnoreCase))
-                        {
-                            result = ptag;
-                            break;
-                        }
+                    tagname = SliceWord(str).ToString();
+                    str = str.Slice(tagname.Length).TrimStart();
+                }
+                
+                // typename
+                if (str.Length > 0 && str[0] != '$')
+                {
+                    typename = SliceWord(str).ToString();
+                    str = str.Slice(typename.Length).TrimStart();
+                }
 
-                        //
-                        pi++;
-                    }
+                // $varname
+                if (str.Length > 0 && str[0] == '$')
+                {
+                    varname = SliceWord(str).ToString();
+                    str = str.Slice(varname.Length).TrimStart();
                 }
+
+                // 
+                description = str.ToString();
+                return true;
             }
 
-            return result;
+            return false;
         }
     }
 }
diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs
index 73d0d5d186..79fa28f693 100644
--- a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs
+++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs
@@ -124,34 +124,6 @@ static bool IsLetterCasingMismatch(string str1, string str2)
 
         private void CheckParams()
         {
-            // Check the compatibility of type hints with PhpDoc, if both exist
-            if (_routine.PHPDocBlock != null)
-            {
-                for (int i = 0; i < _routine.SourceParameters.Length; i++)
-                {
-                    var param = _routine.SourceParameters[i];
-
-                    //// Consider only parameters passed by value, with both typehints and PHPDoc comments
-                    //if (!param.Syntax.IsOut && !param.Syntax.PassedByRef
-                    //    && param.Syntax.TypeHint != null
-                    //    && param.PHPDocOpt != null && param.PHPDocOpt.TypeNamesArray.Length != 0)
-                    //{
-                    //    var tmask = PHPDoc.GetTypeMask(TypeCtx, param.PHPDocOpt.TypeNamesArray, _routine.GetNamingContext());
-                    //    if (!tmask.IsVoid && !tmask.IsAnyType)
-                    //    {
-                    //        var hintType = param.Type;
-                    //        var docType = DeclaringCompilation.GetTypeFromTypeRef(TypeCtx, tmask);
-                    //        if (!docType.IsOfType(hintType))  // REVIEW: not correct, CLR type might result in PhpValue or anything else which is never "of type" specified in PHPDoc
-                    //        {
-                    //            // PHPDoc type is incompatible with type hint
-                    //            _diagnostics.Add(_routine, param.Syntax, ErrorCode.WRN_ParamPhpDocTypeHintIncompatible,
-                    //                param.PHPDocOpt.TypeNames, param.Name, param.Syntax.TypeHint);
-                    //        }
-                    //    }
-                    //}
-                }
-            }
-
             // check source parameters
             var srcparams = _routine.SourceParameters;
             foreach (var p in srcparams)
diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/TypeRef/TypeRefContext.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/TypeRef/TypeRefContext.cs
index b53ae6e128..cb3004b559 100644
--- a/src/Peachpie.CodeAnalysis/FlowAnalysis/TypeRef/TypeRefContext.cs
+++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/TypeRef/TypeRefContext.cs
@@ -883,7 +883,7 @@ public string ToString(TypeRefMask mask)
                 if (types.Count != 0)
                 {
                     types.Sort();
-                    return string.Join(PHPDocBlock.TypeVarDescTag.TypeNamesSeparator.ToString(), types.Distinct());
+                    return string.Join("|", types.Distinct());
                 }
             }
 
diff --git a/src/Peachpie.CodeAnalysis/Peachpie.CodeAnalysis.csproj b/src/Peachpie.CodeAnalysis/Peachpie.CodeAnalysis.csproj
index dcbaa6ff04..341471e2ee 100644
--- a/src/Peachpie.CodeAnalysis/Peachpie.CodeAnalysis.csproj
+++ b/src/Peachpie.CodeAnalysis/Peachpie.CodeAnalysis.csproj
@@ -12,8 +12,8 @@
 
   <ItemGroup>
     <PackageReference Include="Devsense.Php.Parser" Version="$(ParserVersion)" />
-    <PackageReference Include="Devsense.Php.Phar" Version="1.0.15" />
-    <PackageReference Include="Peachpie.Microsoft.CodeAnalysis" Version="3.7.1" />
+    <PackageReference Include="Devsense.Php.Phar" Version="1.0.40" />
+    <PackageReference Include="Peachpie.Microsoft.CodeAnalysis" Version="$(PeachpieMicrosoftCodeAnalysisVersion)" />
     <PackageReference Include="Peachpie.Library.RegularExpressions" Version="$(PeachpieLibraryRegularExpressionsVersion)" />
     <PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
     <PackageReference Include="System.Memory" Version="4.5.4" />
diff --git a/src/Peachpie.CodeAnalysis/PhpResources.resx b/src/Peachpie.CodeAnalysis/PhpResources.resx
index d6e0d95883..a09ee4e8a0 100644
--- a/src/Peachpie.CodeAnalysis/PhpResources.resx
+++ b/src/Peachpie.CodeAnalysis/PhpResources.resx
@@ -162,11 +162,6 @@
                             or specific versions like `5.6.` or `7.0`
 /shortopentag[+|-]          Whether to enable PHP short open tag syntax (&lt;?).
                             Disabled by default.
-/phpdoctypes:&lt;string&gt;       Specifies what PHPDoc is used for strict type
-                            information. Possible values are:
-                            None, FieldTypes, ParameterTypes, ReturnTypes, All
-                            or their combination using vertical bar |.
-/phpdoctypes[+|-]           Shortcut for /phpdoctypes:All or /phpdoctypes:None.
 
                     - SECURITY -
 /delaysign[+|-]             Delay-sign the assembly using only the public
diff --git a/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs b/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs
index 8faf6eaad4..73be065021 100644
--- a/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs
+++ b/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs
@@ -1544,7 +1544,7 @@ internal sealed class GeneratorSemanticsBinder : SemanticsBinder
 
         readonly List<BoundYieldStatement> _yields = new List<BoundYieldStatement>();
 
-        readonly HashSet<AST.LangElement> _yieldsToStatementRootPath = new HashSet<AST.LangElement>();
+        readonly HashSet<AST.ILangElement> _yieldsToStatementRootPath = new HashSet<AST.ILangElement>();
         int _underYieldLikeExLevel = -1;
 
         #endregion
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Php/PhpRoutineSymbolExtensions.cs b/src/Peachpie.CodeAnalysis/Symbols/Php/PhpRoutineSymbolExtensions.cs
index 4a3eb31467..6cf9466246 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Php/PhpRoutineSymbolExtensions.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Php/PhpRoutineSymbolExtensions.cs
@@ -1,4 +1,5 @@
-using Devsense.PHP.Syntax;
+using Devsense.PHP.Ast.DocBlock;
+using Devsense.PHP.Syntax;
 using Devsense.PHP.Syntax.Ast;
 using Microsoft.CodeAnalysis;
 using Pchp.CodeAnalysis.FlowAnalysis;
@@ -78,22 +79,8 @@ internal static TypeSymbol ConstructClrReturnType(SourceRoutineSymbol routine)
             if (IsNotOverriding(routine))
             {
                 // /** @return */
-                var typeCtx = routine.TypeRefContext;
-                if (routine.PHPDocBlock != null && (compilation.Options.PhpDocTypes & PhpDocTypes.ReturnTypes) != 0)
-                {
-                    var returnTag = routine.PHPDocBlock.Returns;
-                    if (returnTag != null && returnTag.TypeNames.Length != 0)
-                    {
-                        var tmask = PHPDoc.GetTypeMask(typeCtx, returnTag.TypeNamesArray, routine.GetNamingContext());
-                        if (!tmask.IsVoid && !tmask.IsAnyType)
-                        {
-                            return compilation.GetTypeFromTypeRef(typeCtx, tmask);
-                        }
-                    }
-                }
-
                 // determine from code flow
-                return compilation.GetTypeFromTypeRef(typeCtx, routine.ResultTypeMask);
+                return compilation.GetTypeFromTypeRef(routine.TypeRefContext, routine.ResultTypeMask);
             }
             else
             {
@@ -379,7 +366,7 @@ internal static IEnumerable<SourceRoutineSymbol> GetAllRoutines(this SourceFileS
         /// <summary>
         /// Gets PHPDoc assoviated with given source symbol.
         /// </summary>
-        internal static bool TryGetPHPDocBlock(this Symbol symbol, out PHPDocBlock phpdoc)
+        internal static bool TryGetPHPDocBlock(this Symbol symbol, out IDocBlock phpdoc)
         {
             phpdoc = symbol?.OriginalDefinition switch
             {
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFieldSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFieldSymbol.cs
index 760579f8e9..76d40c2c80 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFieldSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFieldSymbol.cs
@@ -13,6 +13,7 @@
 using Pchp.CodeAnalysis.Utilities;
 using System.Globalization;
 using System.Threading;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -61,7 +62,7 @@ TypeSymbol IPhpPropertySymbol.ContainingStaticsHolder
         /// <summary>
         /// Optional associated PHPDoc block defining the field type hint.
         /// </summary>
-        internal PHPDocBlock PHPDocBlock { get; }
+        internal IDocBlock PHPDocBlock { get; }
 
         /// <summary>
         /// Declared accessibility - private, protected or public.
@@ -158,7 +159,7 @@ public PropertySymbol FieldAccessorProperty
 
         public SourceFieldSymbol(
             SourceTypeSymbol type, string name, Location location, Accessibility accessibility,
-            PHPDocBlock phpdoc, PhpPropertyKind kind,
+            IDocBlock phpdoc, PhpPropertyKind kind,
             BoundExpression initializer = null,
             ImmutableArray<AttributeData> attributes = default)
         {
@@ -175,7 +176,7 @@ public SourceFieldSymbol(
             PHPDocBlock = phpdoc;
 
             // implicit attributes from PHPDoc
-            var deprecated = phpdoc?.GetElement<PHPDocBlock.DeprecatedTag>();
+            var deprecated = phpdoc.GetDocEntry(AstUtils.DeprecatedTagName);
             if (deprecated != null)
             {
                 // [ObsoleteAttribute(message, false)]
@@ -255,18 +256,6 @@ internal override TypeSymbol GetFieldType(ConsList<FieldSymbol> fieldsBeingBound
                 //return DeclaringCompilation.GetTypeFromTypeRef(typectx, Initializer.TypeRefMask);
             }
 
-            // PHPDoc @var type
-            if ((DeclaringCompilation.Options.PhpDocTypes & PhpDocTypes.FieldTypes) != 0)
-            {
-                var vartag = FindPhpDocVarTag();
-                if (vartag != null && vartag.TypeNamesArray.Length != 0)
-                {
-                    var dummyctx = TypeRefFactory.CreateTypeRefContext(_containingType);
-                    var tmask = PHPDoc.GetTypeMask(dummyctx, vartag.TypeNamesArray, NameUtils.GetNamingContext(_containingType.Syntax));
-                    return DeclaringCompilation.GetTypeFromTypeRef(dummyctx, tmask);
-                }
-            }
-
             // default
             return DeclaringCompilation.CoreTypes.PhpValue;
         }
@@ -286,21 +275,7 @@ internal override TypeSymbol GetFieldType(ConsList<FieldSymbol> fieldsBeingBound
         /// </summary>
         public override bool IsStatic => _fieldKind == PhpPropertyKind.AppStaticField || IsConst; // either field is CLR static field or constant (Literal field must be Static).
 
-        internal PHPDocBlock.TypeVarDescTag FindPhpDocVarTag()
-        {
-            if (PHPDocBlock != null)
-            {
-                foreach (var vartype in PHPDocBlock.Elements.OfType<PHPDocBlock.TypeVarDescTag>())
-                {
-                    if (string.IsNullOrEmpty(vartype.VariableName) || vartype.VariableName.Substring(1) == this.MetadataName)
-                    {
-                        return vartype;
-                    }
-                }
-            }
-
-            return null;
-        }
+        internal IDocEntry FindPhpDocVarTag() => PHPDocBlock.GetDocEntry("@var") ?? PHPDocBlock.GetDocEntry("@staticvar");
 
         public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken))
         {
@@ -316,7 +291,7 @@ internal PHPDocBlock.TypeVarDescTag FindPhpDocVarTag()
                     var vartag = FindPhpDocVarTag();
                     if (vartag != null)
                     {
-                        summary = vartag.Description;
+                        summary = vartag.ToString();
                     }
                 }
             }
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFunctionSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFunctionSymbol.cs
index a9a7a2eed6..26de4b0cd9 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFunctionSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceFunctionSymbol.cs
@@ -13,6 +13,7 @@
 using Pchp.CodeAnalysis.CodeGen;
 using System.Diagnostics;
 using Peachpie.CodeAnalysis;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -90,7 +91,7 @@ internal override TypeSymbol EmitLoadRoutineInfo(CodeGenerator cg)
 
         internal override AstNode Syntax => _syntax;
 
-        internal override PHPDocBlock PHPDocBlock => _syntax.PHPDoc;
+        internal override IDocBlock PHPDocBlock => _syntax.PHPDoc;
 
         internal override IList<Statement> Statements => _syntax.Body.Statements;
 
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceGlobalMethodSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceGlobalMethodSymbol.cs
index 982e204300..93c3e73898 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceGlobalMethodSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceGlobalMethodSymbol.cs
@@ -10,6 +10,7 @@
 using Devsense.PHP.Syntax;
 using Devsense.PHP.Syntax.Ast;
 using Devsense.PHP.Text;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -52,7 +53,7 @@ protected override IEnumerable<ParameterSymbol> BuildImplicitParams()
             yield return new SpecialParameterSymbol(this, DeclaringCompilation.CoreTypes.RuntimeTypeHandle, SpecialParameterSymbol.SelfName, index++);
         }
 
-        protected override IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature, PHPDocBlock phpdocOpt = null)
+        protected override IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature)
         {
             return Array.Empty<SourceParameterSymbol>();
         }
@@ -101,7 +102,7 @@ public override ImmutableArray<Location> Locations
 
         internal override AstNode Syntax => _file.SyntaxTree.Root;
 
-        internal override PHPDocBlock PHPDocBlock => null;
+        internal override IDocBlock PHPDocBlock => null;
 
         internal override PhpCompilation DeclaringCompilation => _file.DeclaringCompilation;
 
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceLambdaSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceLambdaSymbol.cs
index f51ffb4bc9..9082017fec 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceLambdaSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceLambdaSymbol.cs
@@ -10,6 +10,7 @@
 using Devsense.PHP.Text;
 using Microsoft.CodeAnalysis.Operations;
 using Peachpie.Library.RegularExpressions;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -65,10 +66,10 @@ protected override IEnumerable<ParameterSymbol> BuildImplicitParams()
             };
         }
 
-        protected override IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature, PHPDocBlock phpdocOpt = null)
+        protected override IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature)
         {
             // [use params], [formal params]
-            return base.BuildSrcParams(UseParams.Concat(signature.FormalParams), phpdocOpt);
+            return base.BuildSrcParams(UseParams.Concat(signature.FormalParams));
         }
 
         internal override IList<Statement> Statements => _syntax.Body.Statements;
@@ -81,7 +82,7 @@ protected override IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature s
 
         internal override AstNode Syntax => _syntax;
 
-        internal override PHPDocBlock PHPDocBlock => _syntax.PHPDoc;
+        internal override IDocBlock PHPDocBlock => _syntax.PHPDoc;
 
         internal override SourceFileSymbol ContainingFile => Container.GetContainingFileSymbol();
 
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceMethodSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceMethodSymbol.cs
index 1e9353faa1..4e89788b10 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceMethodSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceMethodSymbol.cs
@@ -13,6 +13,7 @@
 using Microsoft.Cci;
 using System.Threading;
 using System.Diagnostics;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -66,7 +67,7 @@ public override IMethodSymbol OverriddenMethod
 
         internal override AstNode Syntax => _syntax;
 
-        internal override PHPDocBlock PHPDocBlock => _syntax.PHPDoc;
+        internal override IDocBlock PHPDocBlock => _syntax.PHPDoc;
 
         internal override IList<Statement> Statements => _syntax.Body?.Statements;
 
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs
index 2f886198d4..961d5ae48f 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs
@@ -10,6 +10,7 @@
 using Devsense.PHP.Syntax;
 using Pchp.CodeAnalysis.Semantics;
 using System.Threading;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -26,8 +27,6 @@ internal sealed class SourceParameterSymbol : ParameterSymbol
         /// </summary>
         readonly int _relindex;
         
-        internal PHPDocBlock.ParamTag PHPDoc { get; }
-
         ImmutableArray<AttributeData> _attributes;
 
         TypeSymbol _lazyType;
@@ -117,7 +116,7 @@ public override FieldSymbol DefaultValueField
         }
         FieldSymbol _lazyDefaultValueField;
 
-        public SourceParameterSymbol(SourceRoutineSymbol routine, FormalParam syntax, int relindex, PHPDocBlock.ParamTag ptagOpt)
+        public SourceParameterSymbol(SourceRoutineSymbol routine, FormalParam syntax, int relindex)
         {
             Contract.ThrowIfNull(routine);
             Contract.ThrowIfNull(syntax);
@@ -139,8 +138,6 @@ public SourceParameterSymbol(SourceRoutineSymbol routine, FormalParam syntax, in
                 ? new SemanticsBinder(DeclaringCompilation, routine.ContainingFile.SyntaxTree, locals: null, routine: routine, self: routine.ContainingType as SourceTypeSymbol)
                     .BindAttributes(phpattrs)
                 : ImmutableArray<AttributeData>.Empty;
-
-            PHPDoc = ptagOpt;
         }
 
         /// <summary>
@@ -249,19 +246,7 @@ TypeSymbol ResolveType()
             }
             var result = DeclaringCompilation.GetTypeFromTypeRef(typeHint, _routine.ContainingType as SourceTypeSymbol, nullable: DefaultsToNull, phpLang: true);
 
-            // 2. optionally type specified in PHPDoc
-            if (result == null && PHPDoc != null && PHPDoc.TypeNamesArray.Length != 0
-                && (DeclaringCompilation.Options.PhpDocTypes & PhpDocTypes.ParameterTypes) != 0)
-            {
-                var typectx = _routine.TypeRefContext;
-                var tmask = FlowAnalysis.PHPDoc.GetTypeMask(typectx, PHPDoc.TypeNamesArray, _routine.GetNamingContext());
-                if (!tmask.IsVoid && !tmask.IsAnyType)
-                {
-                    result = DeclaringCompilation.GetTypeFromTypeRef(typectx, tmask);
-                }
-            }
-
-            // 3 default:
+            // 2. default:
             if (result == null)
             {
                 // TODO: use type from overriden method
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceRoutineSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceRoutineSymbol.cs
index b8b27bc214..85a0d1641b 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceRoutineSymbol.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceRoutineSymbol.cs
@@ -17,6 +17,7 @@
 using System.Globalization;
 using System.Threading;
 using Peachpie.CodeAnalysis.Semantics;
+using Devsense.PHP.Ast.DocBlock;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -116,7 +117,7 @@ internal LocalsTable LocalsTable
         /// <summary>
         /// Optionaly gets routines PHP doc block.
         /// </summary>
-        internal abstract PHPDocBlock PHPDocBlock { get; }
+        internal abstract IDocBlock PHPDocBlock { get; }
 
         /// <summary>
         /// Reference to a containing file symbol.
@@ -245,7 +246,7 @@ public virtual void GetDiagnostics(DiagnosticBag diagnostic)
         /// <summary>
         /// Constructs routine source parameters.
         /// </summary>
-        protected IEnumerable<SourceParameterSymbol> BuildSrcParams(IEnumerable<FormalParam> formalparams, PHPDocBlock phpdocOpt = null)
+        protected IEnumerable<SourceParameterSymbol> BuildSrcParams(IEnumerable<FormalParam> formalparams)
         {
             var pindex = 0; // zero-based relative index
 
@@ -256,15 +257,13 @@ protected IEnumerable<SourceParameterSymbol> BuildSrcParams(IEnumerable<FormalPa
                     continue;
                 }
 
-                var ptag = (phpdocOpt != null) ? PHPDoc.GetParamTag(phpdocOpt, pindex, p.Name.Name.Value) : null;
-
-                yield return new SourceParameterSymbol(this, p, relindex: pindex++, ptagOpt: ptag);
+                yield return new SourceParameterSymbol(this, p, relindex: pindex++);
             }
         }
 
-        protected virtual IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature, PHPDocBlock phpdocOpt = null)
+        protected virtual IEnumerable<SourceParameterSymbol> BuildSrcParams(Signature signature)
         {
-            return BuildSrcParams(signature.FormalParams, phpdocOpt);
+            return BuildSrcParams(signature.FormalParams);
         }
 
         internal virtual ImmutableArray<ParameterSymbol> ImplicitParameters
@@ -296,7 +295,7 @@ internal SourceParameterSymbol[] SourceParameters
             {
                 if (_srcParams == null)
                 {
-                    var srcParams = BuildSrcParams(this.SyntaxSignature, this.PHPDocBlock).ToArray();
+                    var srcParams = BuildSrcParams(this.SyntaxSignature).ToArray();
                     Interlocked.CompareExchange(ref _srcParams, srcParams, null);
                 }
 
@@ -503,7 +502,7 @@ ImmutableArray<AttributeData> PopulateSourceAttributes()
             }
 
             // attributes from PHPDoc
-            var deprecated = PHPDocBlock?.GetElement<PHPDocBlock.DeprecatedTag>();
+            var deprecated = PHPDocBlock.GetDocEntry(AstUtils.DeprecatedTagName);
             if (deprecated != null)
             {
                 // [ObsoleteAttribute(message, false)]
@@ -554,10 +553,16 @@ internal override ObsoleteAttributeData ObsoleteAttributeData
         {
             get
             {
-                var deprecated = this.PHPDocBlock?.GetElement<PHPDocBlock.DeprecatedTag>();
+                var deprecated = this.PHPDocBlock.GetDocEntry(AstUtils.DeprecatedTagName);
                 if (deprecated != null)
                 {
-                    return new ObsoleteAttributeData(ObsoleteAttributeKind.Deprecated, deprecated.Version/*==Text*/, isError: false, diagnosticId: null, urlFormat: null);
+                    return new ObsoleteAttributeData(
+                        ObsoleteAttributeKind.Deprecated,
+                        deprecated.GetEntryText(out var text) ? text : string.Empty,
+                        isError: false,
+                        diagnosticId: null,
+                        urlFormat: null
+                    );
                 }
 
                 return null;
diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceSymbolCollection.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceSymbolCollection.cs
index e482939cd1..0a588e6927 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceSymbolCollection.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceSymbolCollection.cs
@@ -233,7 +233,7 @@ public void AddSyntaxTree(PhpSyntaxTree tree)
             // annotate routines that contain yield
             if (!tree.YieldNodes.IsDefaultOrEmpty)
             {
-                var yieldsInRoutines = new Dictionary<LangElement, List<IYieldLikeEx>>();
+                var yieldsInRoutines = new Dictionary<ILangElement, List<IYieldLikeEx>>();
                 foreach (var y in tree.YieldNodes)
                 {
                     Debug.Assert(y is IYieldLikeEx);
diff --git a/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs b/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs
index f2d105639e..3db6085fa5 100644
--- a/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs
+++ b/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs
@@ -4,9 +4,9 @@
 using System.Diagnostics;
 using System.Linq;
 using System.Text;
+using Devsense.PHP.Ast.DocBlock;
 using Devsense.PHP.Syntax;
 using Microsoft.CodeAnalysis;
-using Pchp.CodeAnalysis.Semantics;
 
 namespace Pchp.CodeAnalysis.Symbols
 {
@@ -23,7 +23,7 @@ public static SynthesizedAttributeData CreateCompilerGeneratedAttribute(this Php
                 ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty);
         }
 
-        public static AttributeData CreateObsoleteAttribute(this PhpCompilation compilation, PHPDocBlock.DeprecatedTag deprecated)
+        public static AttributeData CreateObsoleteAttribute(this PhpCompilation compilation, IDocEntry deprecated)
         {
             if (deprecated == null)
                 throw new ArgumentNullException(nameof(deprecated));
@@ -32,7 +32,7 @@ public static AttributeData CreateObsoleteAttribute(this PhpCompilation compilat
             return new SynthesizedAttributeData(
                 (MethodSymbol)compilation.GetWellKnownTypeMember(WellKnownMember.System_ObsoleteAttribute__ctor),
                     ImmutableArray.Create(
-                        compilation.CreateTypedConstant(deprecated.Version/*NOTE:Version contains the message*/),
+                        compilation.CreateTypedConstant(deprecated.ToString()/*whole @deprecated tag entry*/),
                         compilation.CreateTypedConstant(false/*isError*/)),
                     ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty);
         }
diff --git a/src/Peachpie.CodeAnalysis/Syntax/AdditionalSyntaxProvider.cs b/src/Peachpie.CodeAnalysis/Syntax/AdditionalSyntaxProvider.cs
index ea4d537de6..b1d7641169 100644
--- a/src/Peachpie.CodeAnalysis/Syntax/AdditionalSyntaxProvider.cs
+++ b/src/Peachpie.CodeAnalysis/Syntax/AdditionalSyntaxProvider.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Text;
+using Devsense.PHP.Ast.DocBlock;
 using Devsense.PHP.Syntax;
 using Devsense.PHP.Syntax.Ast;
 using Devsense.PHP.Text;
@@ -49,7 +50,7 @@ public TValue TokenValue
 
         public string TokenText => _provider.TokenText;
 
-        public PHPDocBlock DocComment { get => _provider.DocComment; set => _provider.DocComment = value; }
+        public IDocBlock DocComment { get => _provider.DocComment; set => _provider.DocComment = value; }
 
         public int GetNextToken()
         {
diff --git a/src/Peachpie.CodeAnalysis/Syntax/PhpSourceUnit.cs b/src/Peachpie.CodeAnalysis/Syntax/PhpSourceUnit.cs
index 2e8937dff9..fb5857d1c4 100644
--- a/src/Peachpie.CodeAnalysis/Syntax/PhpSourceUnit.cs
+++ b/src/Peachpie.CodeAnalysis/Syntax/PhpSourceUnit.cs
@@ -43,7 +43,7 @@ public void Parse(NodesFactory factory, IErrorSink<Span> errors,
             {
                 using (var provider = new AdditionalSyntaxProvider(
                     new PhpTokenProvider(
-                        new Lexer(source, Encoding.UTF8, errors, features, 0, state),
+                        new Lexer(source, Encoding.UTF8, errors, features, initialState: state),
                         this),
                     factory,
                     parser.CreateTypeRef))
diff --git a/src/Peachpie.CodeAnalysis/Syntax/PhpTokenProvider.cs b/src/Peachpie.CodeAnalysis/Syntax/PhpTokenProvider.cs
index cfe4e4fd22..06f042fa5b 100644
--- a/src/Peachpie.CodeAnalysis/Syntax/PhpTokenProvider.cs
+++ b/src/Peachpie.CodeAnalysis/Syntax/PhpTokenProvider.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
+using Devsense.PHP.Ast.DocBlock;
 using Devsense.PHP.Syntax;
 using Devsense.PHP.Text;
 using Devsense.PHP.Utilities;
@@ -21,7 +22,7 @@ sealed class PhpTokenProvider : ITokenProvider<TValue, TSpan>, IDisposable
         readonly PhpSourceUnit _sourceunit;
 
         StringTable _strings;
-        PHPDocBlock _docblock;
+        IDocBlock _docblock;
 
         /// <summary>
         /// Buffered tokens.
@@ -80,7 +81,7 @@ public string TokenText
             }
         }
 
-        public PHPDocBlock DocComment
+        public IDocBlock DocComment
         {
             get => _docblock;
             set => _docblock = value;
diff --git a/src/Peachpie.CodeAnalysis/Utilities/AstUtils.cs b/src/Peachpie.CodeAnalysis/Utilities/AstUtils.cs
index 725e188cf9..f942bd54d3 100644
--- a/src/Peachpie.CodeAnalysis/Utilities/AstUtils.cs
+++ b/src/Peachpie.CodeAnalysis/Utilities/AstUtils.cs
@@ -13,6 +13,7 @@
 using Microsoft.CodeAnalysis;
 using Pchp.CodeAnalysis.Semantics;
 using System.Runtime.InteropServices;
+using Pchp.CodeAnalysis.FlowAnalysis;
 
 namespace Pchp.CodeAnalysis
 {
@@ -208,9 +209,15 @@ public static int GetOffset(this PhpSyntaxTree tree, LinePosition linePosition)
 
         /// <summary>
         /// Attribute name determining the field below is app-static instead of context-static.
+        /// @appstatic
         /// </summary>
         public const string AppStaticTagName = "@appstatic";
 
+        /// <summary>
+        /// @deprecated
+        /// </summary>
+        public const string DeprecatedTagName = "@deprecated";
+
         /// <summary>
         /// Lookups notation determining given field as app-static instead of context-static.
         /// </summary>
@@ -223,9 +230,14 @@ public static bool IsAppStatic(this FieldDeclList field)
                 var phpdoc = field.PHPDoc;
                 if (phpdoc != null)
                 {
-                    return phpdoc.Elements
-                        .OfType<PHPDocBlock.UnknownTextTag>()
-                        .Any(t => t.TagName.Equals(AppStaticTagName, StringComparison.OrdinalIgnoreCase));
+                    foreach (var entry in phpdoc)
+                    {
+                        // @appstatic
+                        if (entry.IsDocTag(AppStaticTagName))
+                        {
+                            return true;
+                        }
+                    }
                 }
             }
 
@@ -275,7 +287,7 @@ public static QualifiedName GetAnonymousTypeQualifiedName(this AnonymousTypeDecl
         /// <summary>
         /// Traverses AST and finds closest parent element of desired type.
         /// </summary>
-        public static T FindParentLangElement<T>(LangElement node) where T : LangElement
+        public static T FindParentLangElement<T>(ILangElement node) where T : ILangElement
         {
             while (node != null && !(node is T))
             {
@@ -288,7 +300,7 @@ public static T FindParentLangElement<T>(LangElement node) where T : LangElement
         /// <summary>
         /// Gets containing routine element (function, method or lambda).
         /// </summary>
-        public static LangElement GetContainingRoutine(this LangElement element)
+        public static ILangElement GetContainingRoutine(this ILangElement element)
         {
             while (!(element is MethodDecl || element is FunctionDecl || element is LambdaFunctionExpr || element is GlobalCode || element == null))
             {
@@ -348,16 +360,16 @@ public static Microsoft.CodeAnalysis.Text.TextSpan GetMoveNextSpan(this ForeachS
         sealed class ElementVisitor<TElement> : TreeVisitor
             where TElement : LangElement
         {
-            readonly Func<LangElement, bool> _acceptPredicate;
+            readonly Func<ILangElement, bool> _acceptPredicate;
 
             public List<TElement> Result { get; } = new List<TElement>();
 
-            public ElementVisitor(Func<LangElement, bool> acceptPredicate)
+            public ElementVisitor(Func<ILangElement, bool> acceptPredicate)
             {
                 _acceptPredicate = acceptPredicate;
             }
 
-            public override void VisitElement(LangElement element)
+            public override void VisitElement(ILangElement element)
             {
                 if (element is TElement x)
                 {
@@ -374,7 +386,7 @@ public override void VisitElement(LangElement element)
         /// <summary>
         /// Gets all elements of given type.
         /// </summary>
-        public static List<TElement> SelectElements<TElement>(this LangElement root, Func<LangElement, bool> acceptPredicate)
+        public static List<TElement> SelectElements<TElement>(this LangElement root, Func<ILangElement, bool> acceptPredicate)
             where TElement : LangElement
         {
             var visitor = new ElementVisitor<TElement>(acceptPredicate);