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 (<?). Disabled by default. -/phpdoctypes:<string> 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);