File: Binder\SafeContext.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Diagnostics;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A representation of the program region in which the *referent* of a `ref` is *live*.
    /// Limited to what is expressible in C#.
    /// See also:
    /// - https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#detailed-design
    /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#972-ref-safe-contexts
    /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#16412-safe-context-constraint
    /// </summary>
    /// <remarks>
    /// - A *referent* is the variable being referenced by a `ref`.
    /// - Informally, a variable is *live* if it has storage allocated for it (either on heap or stack).
    /// - In this design, all SafeContexts have a known relationship to all other SafeContexts.
    /// </remarks>
    internal readonly struct SafeContext
    {
        private const uint CallingMethodRaw = 0;
        private const uint ReturnOnlyRaw = 1;
        private const uint CurrentMethodRaw = 2;
 
        /// <summary>
        /// For the purpose of escape verification we operate with the depth of local scopes.
        /// The depth is a uint, with smaller number representing shallower/wider scopes.
        /// Since sibling scopes do not intersect and a value cannot escape from one to another without
        /// escaping to a wider scope, we can use simple depth numbering without ambiguity.
        /// </summary>
        private readonly uint _value;
        private SafeContext(uint value) => _value = value;
 
        /// <summary>
        /// The "calling method" scope that is outside of the containing method/lambda.
        /// If something can escape to this scope, it can escape to any scope in a given method through a ref parameter or return.
        /// </summary>
        public static readonly SafeContext CallingMethod = new SafeContext(CallingMethodRaw);
 
        /// <summary>
        /// The "return-only" scope that is outside of the containing method/lambda.
        /// If something can escape to this scope, it can escape to any scope in a given method or can be returned, but it can't escape through a ref parameter.
        /// </summary>
        public static readonly SafeContext ReturnOnly = new SafeContext(ReturnOnlyRaw);
 
        /// <summary>
        /// The "current method" scope that is just inside the containing method/lambda.
        /// If something can escape to this scope, it can escape to any scope in a given method, but cannot be returned.
        /// </summary>
        public static readonly SafeContext CurrentMethod = new SafeContext(CurrentMethodRaw);
 
        /// <summary>
        /// Gets a SafeContext which is "empty". i.e. which refers to a variable whose storage is never allocated.
        /// </summary>
        public static readonly SafeContext Empty = new SafeContext(uint.MaxValue);
 
        /// <summary>
        /// Gets a SafeContext which is narrower than the given SafeContext.
        /// Used to "enter" a nested local scope.
        /// </summary>
        public SafeContext Narrower()
        {
            Debug.Assert(_value >= ReturnOnlyRaw);
            return new SafeContext(_value + 1);
        }
 
        /// <summary>
        /// Gets a SafeContext which is wider than the given SafeContext.
        /// Used to "exit" a nested local scope.
        /// </summary>
        public SafeContext Wider()
        {
            Debug.Assert(_value >= CurrentMethodRaw);
            return new SafeContext(_value - 1);
        }
 
        public bool IsCallingMethod => _value == CallingMethodRaw;
        public bool IsReturnOnly => _value == ReturnOnlyRaw;
        public bool IsReturnable => _value is CallingMethodRaw or ReturnOnlyRaw;
 
        /// <summary>Returns true if a 'ref' with this SafeContext can be converted to the 'other' SafeContext. Otherwise, returns false.</summary>
        /// <remarks>Generally, a wider SafeContext is convertible to a narrower SafeContext.</remarks>
        public bool IsConvertibleTo(SafeContext other)
            => this._value <= other._value;
 
        /// <summary>
        /// Returns the narrower of two SafeContexts.
        /// </summary>
        /// <remarks>
        /// In other words, this method returns the widest SafeContext which 'this' and 'other' are both convertible to.
        /// If in future we added the concept of unrelated SafeContexts (e.g. to implement 'ref scoped'), this method would perhaps return a Nullable,
        /// for the case that no SafeContext exists which both input SafeContexts are convertible to.
        /// </remarks>
        public SafeContext Intersect(SafeContext other)
            => this.IsConvertibleTo(other) ? other : this;
 
        /// <summary>
        /// Returns the wider of two SafeContexts.
        /// </summary>
        /// <remarks>In other words, this method returns the narrowest SafeContext which can be converted to both 'this' and 'other'.</remarks>
        public SafeContext Union(SafeContext other)
            => this.IsConvertibleTo(other) ? this : other;
 
        /// <summary>Returns true if this SafeContext is the same as 'other' (i.e. for invariant nested conversion).</summary>
        public bool Equals(SafeContext other)
            => this._value == other._value;
 
        public override bool Equals(object? obj)
            => obj is SafeContext other && this.Equals(other);
 
        public override int GetHashCode()
            => unchecked((int)_value);
 
        public static bool operator ==(SafeContext lhs, SafeContext rhs)
            => lhs._value == rhs._value;
 
        public static bool operator !=(SafeContext lhs, SafeContext rhs)
            => lhs._value != rhs._value;
 
        public override string ToString()
            => _value switch
            {
                CallingMethodRaw => "SafeContext<CallingMethod>",
                ReturnOnlyRaw => "SafeContext<ReturnOnly>",
                CurrentMethodRaw => "SafeContext<CurrentMethod>",
                _ => $"SafeContext<{_value}>"
            };
    }
}