File: Binder\LookupResult.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.
 
#nullable disable
 
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// represents one-to-one symbol -> SingleLookupResult filter.
    /// </summary>
    internal delegate SingleLookupResult LookupFilter(Symbol sym);
 
    /// <summary>
    /// A LookupResult summarizes the result of a name lookup within a scope It also allows
    /// combining name lookups from different scopes in an easy way.
    /// 
    /// A LookupResult can be ONE OF:
    ///    empty - nothing found.
    ///    a viable result - this kind of result prevents lookup into further scopes of lower priority.
    ///                      Viable results should be without error; ambiguity is handled in the caller.
    ///                      (Note that handling multiple "viable" results is not the same as in the VB compiler)
    ///    a non-accessible result - this kind of result means that search continues into further scopes of lower priority for
    ///                      a viable result. An error is attached with the inaccessibility errors. Non-accessible results take priority over
    ///                      non-viable results.
    ///    a non-viable result - a result that means that the search continues into further scopes of lower priority for
    ///                          a viable or non-accessible result. An error is attached with the error that indicates
    ///                          why the result is non-viable.  A typical reason would be that it is the wrong kind of symbol.
    /// 
    /// Note that the class is poolable so its instances can be obtained from a pool via GetInstance.
    /// Also it is a good idea to call Free on instances after they no longer needed.
    /// 
    /// The typical pattern is "caller allocates / caller frees" -
    ///    
    ///    var result = LookupResult.GetInstance();
    ///  
    ///    scope.Lookup(result, "goo");
    ///    ... use result ...
    ///         
    ///    result.Clear();
    ///    anotherScope.Lookup(result, "moo");
    ///    ... use result ...
    /// 
    ///    result.Free();   //result and its content is invalid after this
    ///    
    /// 
    /// 
    /// </summary>
    /// <remarks>
    /// Currently LookupResult is intended only for name lookup, not for overload resolution. It is
    /// not clear if overload resolution will work with the structure as is, require enhancements,
    /// or be best served by an alternate mechanism.
    /// 
    /// We might want to extend this to a more general priority scheme.
    /// 
    /// </remarks>
    internal sealed class LookupResult
    {
        // the kind of result.
        private LookupResultKind _kind;
 
        // If there is more than one symbol, they are stored in this list.
        private readonly ArrayBuilder<Symbol> _symbolList;
 
        // the error of the result, if it is NonViable or Inaccessible
        private DiagnosticInfo _error;
 
        private readonly ObjectPool<LookupResult> _pool;
 
        private LookupResult(ObjectPool<LookupResult> pool)
        {
            _pool = pool;
            _kind = LookupResultKind.Empty;
            _symbolList = new ArrayBuilder<Symbol>();
            _error = null;
        }
 
        internal bool IsClear
        {
            get
            {
                return _kind == LookupResultKind.Empty && _error == null && _symbolList.Count == 0;
            }
        }
 
        internal void Clear()
        {
            _kind = LookupResultKind.Empty;
            _symbolList.Clear();
            _error = null;
        }
 
        internal LookupResultKind Kind
        {
            get
            {
                return _kind;
            }
        }
 
        /// <summary>
        /// Return the single symbol if there is exactly one, otherwise null.
        /// </summary>
        internal Symbol SingleSymbolOrDefault
        {
            get
            {
                return (_symbolList.Count == 1) ? _symbolList[0] : null;
            }
        }
 
        internal ArrayBuilder<Symbol> Symbols
        {
            get
            {
                return _symbolList;
            }
        }
 
        internal DiagnosticInfo Error
        {
            get
            {
                return _error;
            }
        }
 
        /// <summary>
        /// Is the result viable with one or more symbols?
        /// </summary>
        internal bool IsMultiViable
        {
            get
            {
                return Kind == LookupResultKind.Viable;
            }
        }
 
        /// <summary>
        /// NOTE: Even there is a single viable symbol, it may be an error type symbol.
        /// </summary>
        internal bool IsSingleViable
        {
            get
            {
                return Kind == LookupResultKind.Viable && _symbolList.Count == 1;
            }
        }
 
        internal static SingleLookupResult Good(Symbol symbol)
        {
            return new SingleLookupResult(LookupResultKind.Viable, symbol, null);
        }
 
        internal static SingleLookupResult WrongArity(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.WrongArity, symbol, error);
        }
 
        internal static SingleLookupResult Empty()
        {
            return new SingleLookupResult(LookupResultKind.Empty, null, null);
        }
 
        internal static SingleLookupResult NotReferencable(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.NotReferencable, symbol, error);
        }
 
        internal static SingleLookupResult StaticInstanceMismatch(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.StaticInstanceMismatch, symbol, error);
        }
 
        internal static SingleLookupResult Inaccessible(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.Inaccessible, symbol, error);
        }
 
        internal static SingleLookupResult NotInvocable(Symbol unwrappedSymbol, Symbol symbol, bool diagnose)
        {
            var diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_NonInvocableMemberCalled, unwrappedSymbol) : null;
            return new SingleLookupResult(LookupResultKind.NotInvocable, symbol, diagInfo);
        }
 
        internal static SingleLookupResult NotLabel(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.NotLabel, symbol, error);
        }
 
        internal static SingleLookupResult NotTypeOrNamespace(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.NotATypeOrNamespace, symbol, error);
        }
 
        internal static SingleLookupResult NotTypeOrNamespace(Symbol unwrappedSymbol, Symbol symbol, bool diagnose)
        {
            // TODO: determine correct diagnosis 
            var diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_BadSKknown, unwrappedSymbol.Name, unwrappedSymbol.GetKindText(), MessageID.IDS_SK_TYPE.Localize()) : null;
            return new SingleLookupResult(LookupResultKind.NotATypeOrNamespace, symbol, diagInfo);
        }
 
        internal static SingleLookupResult NotAnAttributeType(Symbol symbol, DiagnosticInfo error)
        {
            return new SingleLookupResult(LookupResultKind.NotAnAttributeType, symbol, error);
        }
 
        /// <summary>
        /// Set current result according to another.
        /// </summary>
        internal void SetFrom(SingleLookupResult other)
        {
            _kind = other.Kind;
            _symbolList.Clear();
            _symbolList.Add(other.Symbol);
            _error = other.Error;
        }
 
        /// <summary>
        /// Set current result according to another.
        /// </summary>
        internal void SetFrom(LookupResult other)
        {
            _kind = other._kind;
            _symbolList.Clear();
            _symbolList.AddRange(other._symbolList);
            _error = other._error;
        }
 
        internal void SetFrom(DiagnosticInfo error)
        {
            this.Clear();
            _error = error;
        }
 
        // Merge another result with this one, with the current result being prioritized
        // over the other if they are of equal "goodness". Mutates the current result.
        internal void MergePrioritized(LookupResult other)
        {
            if (other.Kind > Kind)
            {
                SetFrom(other);
            }
        }
 
        /// <summary>
        /// Merge another result with this one, with the symbols combined if both
        /// this and other are viable. Otherwise the highest priority result wins (this if equal 
        /// priority and non-viable.)
        /// </summary>
        internal void MergeEqual(LookupResult other)
        {
            if (Kind > other.Kind)
            {
                return;
            }
            else if (other.Kind > Kind)
            {
                this.SetFrom(other);
            }
            else if (Kind != LookupResultKind.Viable)
            {
                // this makes the operator not symmetrical, but so far we do not care.
                // it is really a matter of which error gets reported.
                return;
            }
            else
            {
                // Merging two viable results together. We will always end up with at least two symbols.
                _symbolList.AddRange(other._symbolList);
            }
        }
 
        internal void MergeEqual(SingleLookupResult result)
        {
            if (Kind > result.Kind)
            {
                // existing result is better
            }
            else if (result.Kind > Kind)
            {
                this.SetFrom(result);
            }
            else if ((object)result.Symbol != null)
            {
                // Same goodness. Include all symbols
                _symbolList.Add(result.Symbol);
            }
        }
 
        // global pool
        //TODO: consider if global pool is ok.
        private static readonly ObjectPool<LookupResult> s_poolInstance = CreatePool();
 
        // if someone needs to create a pool
        internal static ObjectPool<LookupResult> CreatePool()
        {
            ObjectPool<LookupResult> pool = null;
            pool = new ObjectPool<LookupResult>(() => new LookupResult(pool), 128); // we rarely need more than 10
            return pool;
        }
 
        internal static LookupResult GetInstance()
        {
            var instance = s_poolInstance.Allocate();
            Debug.Assert(instance.IsClear);
            return instance;
        }
 
        internal void Free()
        {
            this.Clear();
            if (_pool != null)
            {
                _pool.Free(this);
            }
        }
    }
}