using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using JetBrains.Annotations; using Robust.Shared.GameObjects; namespace Robust.Shared.Utility { public static class DebugTools { /// /// An assertion that will always an exception. /// /// Exception message. [Conditional("DEBUG")] [ContractAnnotation("=> halt")] [DoesNotReturn] public static void Assert(string message) { throw new DebugAssertException(message); } /// /// An assertion that will an exception if the /// is not true. /// /// Condition that must be true. [Conditional("DEBUG")] [AssertionMethod] public static void Assert( [AssertionCondition(AssertionConditionType.IS_TRUE)] [DoesNotReturnIf(false)] bool condition) { if (!condition) throw new DebugAssertException(); } /// /// An assertion that will an exception if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertEqual(object? objA, object? objB) { if (ReferenceEquals(objA, objB)) return; if (objA == null || !objA.Equals(objB)) throw new DebugAssertException($"Expected: {objB ?? "null"} but was {objA ?? "null"}"); } /// /// An assertion that will an exception with embedded if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertEqual(object? objA, object? objB, string message) { if (ReferenceEquals(objA, objB)) return; if (objA == null || !objA.Equals(objB)) throw new DebugAssertException($"{message}\nExpected: {objB ?? "null"} but was {objA ?? "null"}"); } /// /// A non-boxing assertion that will an exception if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertEqual(T? objA, T? objB) where T: IEquatable { if (objA == null && objB == null) return; if (objA == null || !objA.Equals(objB)) throw new DebugAssertException($"Expected: {objB?.ToString() ?? "null"} but was {objA?.ToString() ?? "null"}"); } /// /// A non-boxing assertion that will an exception with embedded if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertEqual(T? objA, T? objB, string message) where T: IEquatable { if (objA == null && objB == null) return; if (objA == null || !objA.Equals(objB)) throw new DebugAssertException($"{message}\nExpected: {objB?.ToString() ?? "null"} but was {objA?.ToString() ?? "null"}"); } /// /// An assertion that will an exception if /// and are equal. /// [Conditional("DEBUG")] public static void AssertNotEqual(object? objA, object? objB) { if (ReferenceEquals(objA, objB)) throw new DebugAssertException($"Expected: not {objB ?? "null"}"); if (objA == null || !objA.Equals(objB)) return; throw new DebugAssertException($"Expected: not {objB}"); } /// /// An assertion that will an exception with embedded if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertNotEqual(object? objA, object? objB, string message) { if (ReferenceEquals(objA, objB)) throw new DebugAssertException($"{message}\nExpected: not {objB ?? "null"}"); if (objA == null || !objA.Equals(objB)) return; throw new DebugAssertException($"{message}\nExpected: not {objB}"); } /// /// A non-boxing assertion that will an exception if /// and are equal. /// [Conditional("DEBUG")] public static void AssertNotEqual(T? objA, T? objB) where T: IEquatable { if (objA == null && objB == null) throw new DebugAssertException("Expected: not null"); if (objA == null || !objA.Equals(objB)) return; throw new DebugAssertException($"Expected: not {objB?.ToString() ?? "null"}"); } /// /// An assertion that will an exception with embedded if /// and are not equal. /// [Conditional("DEBUG")] public static void AssertNotEqual(T? objA, T? objB, string message) where T: IEquatable { if (objA == null && objB == null) throw new DebugAssertException($"{message}\nExpected: not null"); if (objA == null || !objA.Equals(objB)) return; throw new DebugAssertException($"{message}\nExpected: not {objB?.ToString() ?? "null"}"); } [Conditional("DEBUG")] public static void AssertOwner(EntityUid? uid, IComponent? component) { if (component == null) return; if (uid == null) throw new DebugAssertException($"Null entity uid cannot own a component. Component: {component.GetType().Name}"); // Whenever .owner is removed this will need to be replaced by something. // As long as components are just reference types, we could just get the component and check if the references are equal? #pragma warning disable CS0618 // Type or member is obsolete if (component.Owner != uid) #pragma warning restore CS0618 // Type or member is obsolete throw new DebugAssertException($"Entity {uid} is not the owner of the component. Component: {component.GetType().Name}"); } /// /// An assertion that will an exception if the /// is not true. /// /// Condition that must be true. /// Exception message. [Conditional("DEBUG")] [AssertionMethod] public static void Assert( [AssertionCondition(AssertionConditionType.IS_TRUE)] [DoesNotReturnIf(false)] bool condition, string message) { if (!condition) throw new DebugAssertException(message); } /// /// An assertion that will an exception if the /// is not true. /// /// Condition that must be true. /// Exception message. [Conditional("DEBUG")] [AssertionMethod] public static void Assert( [AssertionCondition(AssertionConditionType.IS_TRUE)] [DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ref AssertInterpolatedStringHandler message) { if (!condition) throw new DebugAssertException(message.ToStringAndClear()); } /// /// An assertion that will an exception if the /// is . /// /// Condition that must be true. /// Exception message. [Conditional("DEBUG")] [AssertionMethod] public static void AssertNotNull( [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] [System.Diagnostics.CodeAnalysis.NotNull] object? arg, string? message = null) { if (arg == null) { throw new DebugAssertException(message?? "value cannot be null"); } } /// /// An assertion that will an exception if the /// is not . /// /// Condition that must be true. /// Exception message. [Conditional("DEBUG")] [AssertionMethod] public static void AssertNull([AssertionCondition(AssertionConditionType.IS_NULL)] object? arg, string? message = null) { if (arg != null) { throw new DebugAssertException(message ?? "value should be null"); } } /// /// If a debugger is attached to the process, calling this function will cause the /// debugger to break. Equivalent to a software interrupt (INT 3). /// public static void Break() { Debugger.Break(); } [InterpolatedStringHandler] public ref struct AssertInterpolatedStringHandler { private DefaultInterpolatedStringHandler _handler; public AssertInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend) { if (condition) { shouldAppend = false; _handler = default; } else { shouldAppend = true; _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount); } } public string ToStringAndClear() => _handler.ToStringAndClear(); public override string ToString() => _handler.ToString(); public void AppendLiteral(string value) => _handler.AppendLiteral(value); public void AppendFormatted(T value) => _handler.AppendFormatted(value); public void AppendFormatted(T value, string? format) => _handler.AppendFormatted(value, format); } } [Virtual] public class DebugAssertException : Exception { public DebugAssertException() { } public DebugAssertException(string? message) : base(message) { } } }