Click here to Skip to main content
11,580,580 members (68,679 online)
Click here to Skip to main content
Add your own
alternative version

Resolving Symbolic References in a CodeDOM (Part 7)

, 2 Dec 2012 CDDL 6.2K 342 13
Resolving symbolic references in a CodeDOM.
Nova.0.6.exe.zip
Mono.Cecil.dll
Nova.CLI.exe
Nova.CodeDOM.dll
Nova.Examples.exe
Nova.Studio.exe
Nova.Test.exe
Nova.UI.dll
Nova.0.6.zip
Nova.CLI
Properties
Nova.CodeDOM
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Interfaces
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Assemblies
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Parsing
Base
Properties
Rendering
Resolving
Utilities
Mono.Cecil
Reflection
Nova.Examples
Properties
Nova.Studio
Images
About.png
Configuration.png
EditCopy.png
EditCut.png
EditDelete.png
EditPaste.png
EditRedo.png
EditUndo.png
Error.png
Exit.png
FileNew.png
FileOpen.png
FileSave.png
FileSaveAll.png
FileSaveAs.png
Find.png
Help.png
Info.png
Logo.png
Options.png
Print.png
PrintPreview.png
Properties.png
Todo.png
Warning.png
Objects.ico
Properties
Settings.settings
Nova.Test
Properties
Nova.UI
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Properties
Resolving
Utilties
// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mono.Cecil;

using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
using Nova.Utilities;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a symbolic reference that hasn't been resolved to a direct reference (includes references that
    /// are unresolved because they are ambiguous, or because the type arguments or method arguments don't match
    /// in number or type).
    /// </summary>
    public class UnresolvedRef : TypeRefBase
    {
        #region /* FIELDS */

        protected ResolveCategory _resolveCategory;  // The targeted object category

        protected bool _resolving;           // Used to prevent infinite recursion
        protected MatchCandidates _matches;  // Objects with a matching name (either complete or partial matches)

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, bool isFirstOnLine, ResolveCategory resolveCategory)
            : base(name, isFirstOnLine)
        {
            _resolveCategory = resolveCategory;
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, bool isFirstOnLine)
            : base(name, isFirstOnLine)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name)
            : base(name, false)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, ResolveCategory resolveCategory, int lineNumber, int column)
            : this(name, false, resolveCategory)
        {
            _lineNumber = lineNumber;
            _columnNumber = (ushort)column;
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, ResolveCategory resolveCategory)
            : this(name, false, resolveCategory)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(SymbolicRef symbolicRef, ResolveCategory resolveCategory)
            : this(symbolicRef.Name, symbolicRef.IsFirstOnLine)
        {
            _resolveCategory = resolveCategory;
            Parent = symbolicRef.Parent;
            SetLineCol(symbolicRef);
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(TypeRefBase typeRefBase, ResolveCategory resolveCategory, bool copyTypeArguments)
            : this(typeRefBase.Name, typeRefBase.IsFirstOnLine)
        {
            _resolveCategory = resolveCategory;
            Parent = typeRefBase.Parent;
            SetLineCol(typeRefBase);
            if (copyTypeArguments)
                _typeArguments = typeRefBase.TypeArguments;
            _arrayRanks = typeRefBase.ArrayRanks;
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        protected internal UnresolvedRef(Token token, ResolveCategory resolveCategory)
            : this(token.NonVerbatimText, token.IsFirstOnLine, resolveCategory)
        {
            NewLines = token.NewLines;
            SetLineCol(token);
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        protected internal UnresolvedRef(Token token)
            : this(token, ResolveCategory.Unspecified)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        protected internal UnresolvedRef(string name, Token token)
            : this(token)
        {
            // Initialize with token first, but then override to the specified name
            _reference = name;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The name of the <see cref="UnresolvedRef"/>.
        /// </summary>
        public override string Name
        {
            get { return (string)_reference; }
        }

        /// <summary>
        /// The descriptive category of the <see cref="SymbolicRef"/>.
        /// </summary>
        public override string Category
        {
            get { return "unresolved"; }
        }

        /// <summary>
        /// The <see cref="ResolveCategory"/>.
        /// </summary>
        public ResolveCategory ResolveCategory
        {
            get { return _resolveCategory; }
        }

        /// <summary>
        /// True if there are some matches.
        /// </summary>
        public bool HasMatches
        {
            get { return (_matches != null && _matches.Count > 0); }
        }

        /// <summary>
        /// True if there is a complete match.
        /// </summary>
        public bool HasCompleteMatch
        {
            get { return (_matches != null && _matches.IsCompleteMatch); }
        }

        /// <summary>
        /// The collection of <see cref="MatchCandidate"/>s.
        /// </summary>
        public MatchCandidates Matches
        {
            get { return _matches; }
            set { _matches = value; }
        }

        /// <summary>
        /// True if this <see cref="UnresolvedRef"/> is the target of an assignment.
        /// </summary>
        public bool IsTargetOfAssignment
        {
            get
            {
                // Handle "expression.UnresolvedRef = ..."
                if (_parent is Dot && _parent.Parent is Assignment && ((Assignment)_parent.Parent).Left == _parent && ((Dot)_parent).Right == this)
                    return true;
                // Handle "expression[...] = ..." (where the hidden IndexerRef is the UnresolvedRef
                if (_parent is Index && _parent.Parent is Assignment && ((Assignment)_parent.Parent).Left == _parent && _parent.HiddenRef == this)
                    return true;
                // Handle "UnresolvedRef = ..."
                if (_parent is Assignment && ((Assignment)_parent).Left == this)
                {
                    // Don't treat as the target of an assignment in the special case of an "embedded collection" initialization.
                    // This is identified by the both the parent AND the right side of the assignment being an Initializer.  In such
                    // a case, the property being assigned should be a collection, and the getter will be used instead of the setter
                    // and the items in the Initializer will be added to the existing collection.
                    Assignment assignment = (Assignment)_parent;
                    return !(assignment.Parent is Initializer && assignment.Right is Initializer);
                }
                return false;
            }
        }

        /// <summary>
        /// True if this <see cref="UnresolvedRef"/> represents a method group.
        /// </summary>
        public bool IsMethodGroup
        {
            get
            {
                // Method groups will have a category of Expression (because they have no parens)
                if (_matches != null && _matches.Count > 0 && _resolveCategory == ResolveCategory.Expression)
                    return Enumerable.Any(_matches, delegate(MatchCandidate match) { return match.Object is MethodDeclBase || match.Object is MethodDefinition || match.Object is MethodInfo; });
                return false;
            }
        }

        /// <summary>
        /// Returns true if the UnresolvedRef represents an explicit interface implementation.
        /// </summary>
        public bool IsExplicitInterfaceImplementation
        {
            get
            {
                if (Parent is Dot)
                {
                    if (Parent.Parent is MethodDeclBase)
                        return (((MethodDeclBase)Parent.Parent).ExplicitInterfaceExpression == Parent);
                    if (Parent.Parent is PropertyDeclBase)
                        return (((PropertyDeclBase)Parent.Parent).ExplicitInterfaceExpression == Parent);
                }
                return false;
            }
        }

        #endregion

        #region /* STATIC METHODS */

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/> from the specified <see cref="Token"/>.
        /// </summary>
        public static UnresolvedRef Create(Token identifier)
        {
            return (identifier != null ? new UnresolvedRef(identifier) : null);
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Reset all resolution members.
        /// </summary>
        /// <param name="notify">Pass <c>false</c> to NOT send notifications.</param>
        public void ResetResolutionMembers(bool notify)
        {
            _matches = null;

            // Clear any existing error messages from a previous resolve pass
            RemoveAllMessages(MessageSource.Resolve, notify);
        }

        /// <summary>
        /// Reset all resolution members.
        /// </summary>
        public void ResetResolutionMembers()
        {
            ResetResolutionMembers(true);
        }

        /// <summary>
        /// Always <c>true</c>.
        /// </summary>
        public override bool IsPossibleDelegateType
        {
            get { return true; }
        }

        /// <summary>
        /// Determine if the current reference refers to the same code object as the specified reference.
        /// </summary>
        public override bool IsSameRef(SymbolicRef symbolicRef)
        {
            UnresolvedRef unresolvedRef = (symbolicRef is AliasRef ? ((AliasRef)symbolicRef).Alias.Expression.SkipPrefixes() : symbolicRef) as UnresolvedRef;
            if (unresolvedRef == null || (string)Reference != (string)unresolvedRef.Reference)
                return false;

            // The strings of the UnresolvedRefs match, but we have to also verify that any Dot prefixes
            // match - if either side has one, they must match, otherwise neither side can have one.
            Dot parentDot = _parent as Dot;
            Dot parentDot2 = symbolicRef.Parent as Dot;
            SymbolicRef dotPrefix = (parentDot != null && parentDot.Right == this ? parentDot.Left as SymbolicRef : null);
            SymbolicRef dotPrefix2 = (parentDot2 != null && parentDot2.Right == this ? parentDot2.Left as SymbolicRef : null);
            return (dotPrefix == null || dotPrefix2 == null || dotPrefix.IsSameRef(dotPrefix2));
        }

        /// <summary>
        /// Calculate a hash code for the referenced object which is the same for all references where IsSameRef() is true.
        /// </summary>
        public override int GetIsSameRefHashCode()
        {
            // Make the hash codes as unique as possible while still ensuring that they are identical
            // for any objects for which IsSameRef() returns true.
            int hashCode = base.GetIsSameRefHashCode();
            if (_parent is Dot && ((Dot)_parent).Right == this && ((Dot)_parent).Left is SymbolicRef)
                hashCode ^= ((SymbolicRef)((Dot)_parent).Left).GetIsSameRefHashCode();
            return hashCode;
        }

        /// <summary>
        /// Determine if the specified TypeRefBase refers to the same generic type, regardless of actual type arguments.
        /// </summary>
        public override bool IsSameGenericType(TypeRefBase typeRefBase)
        {
            return (typeRefBase is UnresolvedRef && (_typeArguments != null ? _typeArguments.Count : 0) == typeRefBase.TypeArgumentCount && Name == typeRefBase.Name);
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            UnresolvedRef clone = (UnresolvedRef)base.Clone();
            clone.ResetResolutionMembers(false);
            return clone;
        }

        /// <summary>
        /// Dispose the <see cref="UnresolvedRef"/>.
        /// </summary>
        public override void Dispose()
        {
            // Clear any matches and remove all messages if disposed
            ResetResolutionMembers();
            _parent = null;
        }

        /// <summary>
        /// Get the full name of the object, including the namespace name.
        /// </summary>
        public override string GetFullName()
        {
            return Reference as string;
        }

        #endregion

        #region /* PARSING */

        internal static new void AddParsePoints()
        {
            // Parse generic type and method references and arrays here in UnresolvedRef, because they may
            // or may not parse as resolved.  Built-in types and '?' nullable types are parsed in TypeRef,
            // because they will parse as resolved.

            // Use a parse-priority of 100 (IndexerDecl uses 0, Index uses 200, Attribute uses 300)
            Parser.AddParsePoint(ParseTokenArrayStart, 100, ParseArrayRanks);

            // Use a parse-priority of 100 (GenericMethodDecl uses 0, LessThan uses 200)
            Parser.AddParsePoint(ParseTokenArgumentStart, 100, ParseTypeArguments);
            // Support alternate symbols for doc comments:
            // Use a parse-priority of 100 (GenericMethodDecl uses 0, PropertyDeclBase uses 200, BlockDecl uses 300, Initializer uses 400)
            Parser.AddParsePoint(ParseTokenAltArgumentStart, 100, ParseAltTypeArguments);
        }

        /// <summary>
        /// Parse array ranks.
        /// </summary>
        public static Expression ParseArrayRanks(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match an array rank pattern
            // (otherwise abort so it can be parsed as an array index or Attribute)
            if (parser.HasUnusedIdentifier && PeekArrayRanks(parser))
                return new UnresolvedRef(parser, parent, true, false);
            return null;
        }

        /// <summary>
        /// Parse type arguments.
        /// </summary>
        public static Expression ParseTypeArguments(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match a type argument list pattern
            // (otherwise abort so that the LessThan operator will get a chance to parse it)
            if (parser.HasUnusedIdentifier && PeekTypeArguments(parser, ParseTokenArgumentEnd, flags))
                return new UnresolvedRef(parser, parent, false, true);
            return null;
        }

        /// <summary>
        /// Parse type arguments using the alternate delimiters.
        /// </summary>
        public static Expression ParseAltTypeArguments(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match a type argument list pattern
            // (otherwise abort so that PropertyDeclBase will get a chance to parse it)
            // Only supported inside documentation comments - subroutines will look for the
            // appropriate delimiters according to the parser state.
            if (parser.InDocComment && parser.HasUnusedIdentifier && PeekTypeArguments(parser, ParseTokenAltArgumentEnd, flags))
                return new UnresolvedRef(parser, parent, false, true);
            return null;
        }

        /// <summary>
        /// Construct an unresolved reference to an array type, or generic type.
        /// </summary>
        protected UnresolvedRef(Parser parser, CodeObject parent, bool isArray, bool isGeneric)
            : base(parser, parent)
        {
            Token token = parser.RemoveLastUnusedToken();
            _reference = token.NonVerbatimText;  // Get the type name
            NewLines = token.NewLines;
            SetLineCol(token);
            
            if (isArray)
            {
                MoveCommentsAsPost(token);   // Get any comments after the identifier
                ParseArrayRanks(parser);     // Parse the array ranks
            }
            else if (isGeneric)
            {
                MoveCommentsAsPost(token);   // Get any comments after the identifier
                _typeArguments = ParseTypeArgumentList(parser, this);  // Parse the type arguments

                // Check for array ranks on the generic type
                if (parser.TokenText == ParseTokenArrayStart && PeekArrayRanks(parser))
                    ParseArrayRanks(parser);
            }
        }

        #endregion

        #region /* RESOLVING */

        /// <summary>
        /// Resolve the unresolved reference, using the specified <see cref="ResolveCategory"/> and <see cref="ResolveFlags"/>.
        /// </summary>
        public override CodeObject Resolve(ResolveCategory resolveCategory, ResolveFlags flags)
        {
            if (_resolving || flags.HasFlag(ResolveFlags.Unresolve))
                return this;
            _resolving = true;

            SymbolicRef resultRef;
            try
            {
                // Set the resolve category, and reset all members related to the resolving process
                _resolveCategory = resolveCategory;
                ResetResolutionMembers();

                // Resolve any type arguments first
                ChildListHelpers.Resolve(TypeArguments, ResolveCategory.Type, flags);

                // Attempt to resolve this unresolved reference
                resultRef = new Resolver(this, resolveCategory, flags).Resolve();
            }
            finally
            {
                _resolving = false;
            }
            return resultRef;
        }

        /// <summary>
        /// Returns true if the code object is an <see cref="UnresolvedRef"/> or has any <see cref="UnresolvedRef"/> children.
        /// </summary>
        public override bool HasUnresolvedRef()
        {
            return true;
        }

        /// <summary>
        /// Conditionally add the candidate to the collection (depending upon what is already in it).
        /// </summary>
        /// <param name="candidate">The MatchCandidate object.</param>
        /// <returns>True if a complete match exists, otherwise false.</returns>
        public bool AddMatch(MatchCandidate candidate)
        {
            bool isMethodGroup;
            if (_matches != null)
            {
                // If we already have a "method group", then we must ignore any non-method candidates
                isMethodGroup = _matches.IsMethodGroup;
                if (isMethodGroup && !candidate.IsMethod)
                    return _matches.IsCompleteMatch;
            }
            else
            {
                // If the first match is a method, and we have an Expression category, then we have a "method group"
                isMethodGroup = (_resolveCategory == ResolveCategory.Expression && candidate.IsMethod);
            }

            // Process the results of the match
            if (candidate.IsCompleteMatch)
            {
                // The match was complete - purge any existing partial matches and save it
                if (_matches == null || !_matches.IsCompleteMatch)
                    _matches = new MatchCandidates(isMethodGroup, true, true);
                _matches.Add(candidate);
            }
            else if (candidate.IsCategoryMatch && (_matches == null || !_matches.IsCompleteMatch))
            {
                // The category matched, but not the type or method arguments or static mode, so this
                // is considered a partial match.  Purge any existing wrong-category matches and save it.
                if (_matches == null || !_matches.IsCategoryMatch)
                    _matches = new MatchCandidates(isMethodGroup, true, false);
                _matches.Add(candidate);
            }
            else if (_matches == null || (!_matches.IsCompleteMatch && !_matches.IsCategoryMatch))
            {
                // Only the name matched, but save it if we don't have anything better
                if (_matches == null)
                    _matches = new MatchCandidates(isMethodGroup, false, false);
                _matches.Add(candidate);
            }
            // Any matches which are inferior to existing matches are discarded

            return _matches.IsCompleteMatch;
        }

        /// <summary>
        /// Create a reference to the specified code object based upon its type, and copy all
        /// appropriate fields from the current UnresolvedRef object if so instructed.
        /// </summary>
        public SymbolicRef CreateRef(object obj, bool copyAll)
        {
            SymbolicRef symbolicRef = null;
            if (obj is INamedCodeObject)
            {
                if (obj is ITypeDecl)
                    symbolicRef = ((ITypeDecl)obj).CreateRef(IsFirstOnLine, TypeArguments, ArrayRanks);
                else if (obj is GenericMethodDecl)
                    symbolicRef = ((GenericMethodDecl)obj).CreateRef(IsFirstOnLine, TypeArguments);
                else if (obj is ConstructorDecl)
                    symbolicRef = ((ConstructorDecl)obj).CreateRef(IsFirstOnLine);
                else
                    symbolicRef = ((INamedCodeObject)obj).CreateRef(IsFirstOnLine);
            }
            else if (obj is MemberReference)
            {
                if (obj is TypeReference)  // TypeDefinition or GenericParameter
                    symbolicRef = TypeRef.Create((TypeReference)obj, IsFirstOnLine, TypeArguments, ArrayRanks);
                else if (obj is MethodDefinition)
                {
                    if (_resolveCategory == ResolveCategory.OperatorOverload)
                        symbolicRef = new OperatorRef((MethodDefinition)obj);
                    else
                        symbolicRef = MethodRef.Create((MethodDefinition)obj, IsFirstOnLine, TypeArguments);
                }
                else if (obj is PropertyDefinition)
                    symbolicRef = PropertyRef.Create((PropertyDefinition)obj, IsFirstOnLine);
                else if (obj is FieldDefinition)
                {
                    FieldDefinition fieldDefinition = (FieldDefinition)obj;
                    if (fieldDefinition.DeclaringType.IsEnum)
                        symbolicRef = new EnumMemberRef(fieldDefinition, IsFirstOnLine);
                    else
                        symbolicRef = new FieldRef(fieldDefinition, IsFirstOnLine);
                }
                else if (obj is EventDefinition)
                    symbolicRef = new EventRef((EventDefinition)obj, IsFirstOnLine);
            }
            else if (obj is ParameterDefinition)
                symbolicRef = new ParameterRef((ParameterDefinition)obj, IsFirstOnLine);
            else if (obj is MemberInfo)
            {
                if (obj is Type)
                    symbolicRef = TypeRef.Create((Type)obj, IsFirstOnLine, TypeArguments, ArrayRanks);
                else if (obj is MethodInfo)
                {
                    if (_resolveCategory == ResolveCategory.OperatorOverload)
                        symbolicRef = new OperatorRef((MethodInfo)obj);
                    else
                        symbolicRef = new MethodRef((MethodInfo)obj, IsFirstOnLine, TypeArguments);
                }
                else if (obj is ConstructorInfo)
                    symbolicRef = new ConstructorRef((ConstructorInfo)obj, IsFirstOnLine);
                else if (obj is PropertyInfo)
                    symbolicRef = PropertyRef.Create((PropertyInfo)obj, IsFirstOnLine);
                else if (obj is FieldInfo)
                {
                    FieldInfo fieldInfo = (FieldInfo)obj;
                    if (fieldInfo.DeclaringType != null && fieldInfo.DeclaringType.IsEnum)
                        symbolicRef = new EnumMemberRef(fieldInfo, IsFirstOnLine);
                    else
                        symbolicRef = new FieldRef(fieldInfo, IsFirstOnLine);
                }
                else if (obj is EventInfo)
                    symbolicRef = new EventRef((EventInfo)obj, IsFirstOnLine);
            }
            else if (obj is ParameterInfo)
                symbolicRef = new ParameterRef((ParameterInfo)obj, IsFirstOnLine);

            if (symbolicRef != null && copyAll)
            {
                // Copy the parent, formatting, and move any non-message annotations
                symbolicRef.Parent = _parent;
                symbolicRef.CopyFormatting(this);
                RemoveAllMessages();
                symbolicRef.Annotations = Annotations;
                Annotations = null;
                symbolicRef.SetLineCol(this);
            }
            return symbolicRef;
        }

        /// <summary>
        /// Evaluate the type of the <see cref="Expression"/>.
        /// </summary>
        /// <returns>The resulting <see cref="TypeRef"/> or <see cref="UnresolvedRef"/>.</returns>
        public override TypeRefBase EvaluateType(bool withoutConstants)
        {
            // If we found multiple complete matches, or one or more partial matches, try to determine if an
            // unambiguous common type exists that we can resolve to.  This is done to prevent "cascading" errors
            // where possible.  We do NOT determine a "minimum" common type here, but only an exact common type,
            // such as the common return type of several overloaded methods from which an exact match couldn't be
            // found.  If we can't evaluate to a precise type, we will evaluate to the UnresolvedRef itself, so
            // that "EvaluateType().FindTypeArgument(" can be used to evaluate common type arguments from the
            // return types of multiple matches.  We also don't want to be misleading by evaluating to a minimum
            // common base type - instead, EvaluateToMinimumType() must be explicitly called.
            if (_matches != null && _matches.IsCategoryMatch)
            {
                TypeRefBase typeRefBase = null;
                switch (_resolveCategory)
                {
                    case ResolveCategory.Type:
                        // Nothing to do here for types - if there's more than one, then they're not the same
                        break;
                    case ResolveCategory.Indexer:
                    case ResolveCategory.Method:
                        // Nothing to do for Indexers or Methods - just return the UnresolvedRef.  However, Call and
                        // Index can check for an UnresolvedRef and evaluate to any common return type among the matches.
                        break;
                    case ResolveCategory.Property:
                        // If there are one or more possible matches, evaluate to the return type if all are the same
                        // (very rare for properties, but could happen if two exist in error with the same name)
                        foreach (MatchCandidate candidate in _matches)
                        {
                            TypeRefBase currentRef = PropertyRef.GetPropertyType(candidate.Object);
                            if (currentRef == null)
                            {
                                typeRefBase = null;
                                break;
                            }
                            currentRef = currentRef.EvaluateTypeArgumentTypes(this);
                            if (typeRefBase == null)
                                typeRefBase = currentRef;
                            else if (!currentRef.IsSameRef(typeRefBase))
                            {
                                typeRefBase = null;
                                break;
                            }
                        }
                        break;
                    case ResolveCategory.Constructor:
                    case ResolveCategory.Attribute:
                        // If there are one or more possible matches, evaluate to the return type if all are the same
                        // (for these categories, a type effectively represents the "return type" of a constructor)
                        foreach (MatchCandidate candidate in _matches)
                        {
                            TypeRefBase currentRef = ConstructorRef.GetReturnType(this, candidate.Object);
                            if (currentRef == null)
                            {
                                typeRefBase = null;
                                break;
                            }
                            currentRef = currentRef.EvaluateTypeArgumentTypes(this);
                            if (typeRefBase == null)
                                typeRefBase = currentRef;
                            else if (!currentRef.IsSameRef(typeRefBase))
                            {
                                typeRefBase = null;
                                break;
                            }
                        }
                        break;
                    case ResolveCategory.Expression:
                        // If there are one or more possible matches, evaluate to the type if all are the same
                        // (all possible variable/parameter/field/property references might have the same type).
                        foreach (MatchCandidate candidate in _matches)
                        {
                            object obj = candidate.Object;
                            TypeRefBase currentRef = null;
                            if (obj is CodeObject)
                            {
                                if (obj is IVariableDecl)
                                {
                                    Expression type = ((IVariableDecl)obj).Type;
                                    if (type != null)
                                        currentRef = type.EvaluateType(withoutConstants);
                                }
                            }
                            else if (obj is IMemberDefinition)
                            {
                                TypeReference typeReference = null;
                                if (obj is PropertyDefinition)
                                    typeReference = ((PropertyDefinition)obj).PropertyType;
                                else if (obj is FieldDefinition)
                                    typeReference = ((FieldDefinition)obj).FieldType;
                                if (typeReference != null)
                                    currentRef = TypeRef.Create(typeReference);
                            }
                            else //if (obj is MemberInfo)
                            {
                                Type type = null;
                                if (obj is PropertyInfo)
                                    type = ((PropertyInfo)obj).PropertyType;
                                else if (obj is FieldInfo)
                                    type = ((FieldInfo)obj).FieldType;
                                if (type != null)
                                    currentRef = TypeRef.Create(type);
                            }
                            if (currentRef == null)
                            {
                                typeRefBase = null;
                                break;
                            }
                            currentRef = currentRef.EvaluateTypeArgumentTypes(this);
                            if (typeRefBase == null)
                                typeRefBase = currentRef;
                            else if (!currentRef.IsSameRef(typeRefBase))
                            {
                                typeRefBase = null;
                                break;
                            }
                        }
                        break;
                }
                if (typeRefBase != null)
                    return typeRefBase;
            }

            // By default, just evaluate to the UnresolvedRef itself
            return this;
        }

        /// <summary>
        /// Evaluate to the minimum common type of multiple possible matches.
        /// </summary>
        public TypeRefBase EvaluateToMinimumType()
        {
            // If we found multiple complete matches, or one or more partial matches, try to determine a minimum
            // common type, and evaluate to it.  This is done to prevent "cascading" errors whenever possible.
            // By default, even if there were no matches at all, evaluate to type 'object'.
            if (_matches != null && _matches.IsCategoryMatch)
            {
                TypeRefBase typeRefBase = null;
                switch (_resolveCategory)
                {
                    case ResolveCategory.Type:
                        // If there are one or more possible matches, evaluate to the minimum common type
                        foreach (MatchCandidate candidate in _matches)
                        {
                            TypeRefBase currentRef = (TypeRefBase)CreateRef(candidate.Object, false);
                            typeRefBase = (typeRefBase == null ? currentRef : TypeRef.GetCommonType(typeRefBase, currentRef));
                        }
                        break;
                    case ResolveCategory.Property:
                        // If there are one or more possible matches, evaluate to the minimum common return type
                        // (very rare for properties, but could happen if two exist in error with the same name)
                        foreach (MatchCandidate candidate in _matches)
                        {
                            TypeRefBase currentRef = PropertyRef.GetPropertyType(candidate.Object);
                            typeRefBase = (typeRefBase == null ? currentRef : TypeRef.GetCommonType(typeRefBase, currentRef));
                        }
                        break;
                    case ResolveCategory.Indexer:
                    case ResolveCategory.Method:
                        // Nothing to do for Indexers or Methods - just return the UnresolvedRef.  However, Call and
                        // Index can check for an UnresolvedRef and evaluate to any common return type among the matches.
                        break;
                    case ResolveCategory.Constructor:
                    case ResolveCategory.Attribute:
                        // If there are one or more possible matches, evaluate to the minimum common type
                        foreach (MatchCandidate candidate in _matches)
                        {
                            TypeRefBase currentRef = ConstructorRef.GetReturnType(this, candidate.Object);
                            typeRefBase = (typeRefBase == null ? currentRef : TypeRef.GetCommonType(typeRefBase, currentRef));
                        }
                        break;
                    case ResolveCategory.Expression:
                        // If there are one or more possible matches, evaluate to the minimum common type
                        foreach (MatchCandidate candidate in _matches)
                        {
                            object obj = candidate.Object;
                            TypeRefBase currentRef = null;
                            if (obj is CodeObject)
                            {
                                if (obj is IVariableDecl)
                                {
                                    Expression type = ((IVariableDecl)obj).Type;
                                    if (type != null)
                                        currentRef = type.EvaluateType();
                                }
                            }
                            else if (obj is IMemberDefinition)
                            {
                                TypeReference typeReference = null;
                                if (obj is PropertyDefinition)
                                    typeReference = ((PropertyDefinition)obj).PropertyType;
                                else if (obj is FieldDefinition)
                                    typeReference = ((FieldDefinition)obj).FieldType;
                                if (typeReference != null)
                                    currentRef = TypeRef.Create(typeReference);
                            }
                            else //if (obj is MemberInfo)
                            {
                                Type type = null;
                                if (obj is PropertyInfo)
                                    type = ((PropertyInfo)obj).PropertyType;
                                else if (obj is FieldInfo)
                                    type = ((FieldInfo)obj).FieldType;
                                if (type != null)
                                    currentRef = TypeRef.Create(type);
                            }

                            typeRefBase = (typeRefBase == null ? currentRef : TypeRef.GetCommonType(typeRefBase, currentRef));
                        }
                        break;
                }
                if (typeRefBase != null)
                    return typeRefBase;
            }

            // By default, just evaluate to type 'object'
            return TypeRef.ObjectRef;
        }

        /// <summary>
        /// Get the minimum common type for the specified delegate parameter, from multiple possible matches.
        /// </summary>
        public TypeRefBase GetDelegateParameterType(ArgumentsOperator argumentsOperator, int argumentIndex)
        {
            // If we found multiple complete matches, or one or more partial matches, try to determine a minimum
            // common type for the specified delegate parameter, and evaluate to it.  This is done to prevent
            // "cascading" errors whenever possible.
            TypeRefBase parameterTypeRef = null;
            if (_matches != null && _matches.IsCategoryMatch)
            {
                // Determine a "minimum" delegate parameter type from the possible matches
                foreach (MatchCandidate candidate in _matches)
                {
                    TypeRefBase currentTypeRef = argumentsOperator.GetDelegateParameterType(candidate.Object, argumentIndex);
                    parameterTypeRef = (parameterTypeRef == null ? currentTypeRef : TypeRef.GetCommonType(parameterTypeRef, currentTypeRef));
                }
            }
            return parameterTypeRef;
        }

        /// <summary>
        /// Resolve an existing method group using the specified delegate type.
        /// </summary>
        public UnresolvedRef ResolveMethodGroup(TypeRefBase delegateType)
        {
            // Make a copy of the current method group (don't Clone it, because we don't want to copy matches or messages),
            // setting the Parent to the delegate Type (yes, it's ugly, but it works).
            UnresolvedRef unresolvedRef = new UnresolvedRef(Name, ResolveCategory, LineNumber, ColumnNumber) { Parent = delegateType };
            if (HasArrayRanks)
                unresolvedRef.ArrayRanks = new List<int>(_arrayRanks);
            // The method overload logic (parameter matching and better-method determination) is handled by Resolver.AddMatch(),
            // so create a Resolver instance, and feed the existing method objects to it.
            Resolver resolver = new Resolver(unresolvedRef, ResolveCategory.Expression, ResolveFlags.Quiet);
            foreach (MatchCandidate match in Matches)
                resolver.AddMatch(match.Object);
            // Return the resulting new method group, which might now have a single method that is an exact match, or perhaps
            // still more than one method (with either exact or partial matches).
            return unresolvedRef;
        }

        /// <summary>
        /// Determine the return type of a method group, or null if it can't be determined.
        /// </summary>
        public TypeRefBase MethodGroupReturnType()
        {
            // Return either the type of a single match, or the common type of multiple matches
            TypeRefBase typeRefBase = null;
            if (_matches != null)
            {
                // We must handle indexers in addition to methods, since they also have parameters and can be overloaded
                Expression parentExpression = this;
                if (Parent is Index && Parent.HiddenRef == this)
                    parentExpression = ((Index)Parent).Expression;
                foreach (MatchCandidate candidate in _matches)
                {
                    object obj = candidate.Object;
                    TypeRefBase currentRef = null;
                    if (obj is MethodDeclBase)
                    {
                        Expression type = ((MethodDeclBase)obj).ReturnType;
                        if (type != null)
                            currentRef = type.EvaluateType();
                    }
                    else if (obj is IndexerDecl)
                    {
                        Expression type = ((IndexerDecl)obj).Type;
                        if (type != null)
                            currentRef = type.EvaluateType();
                    }
                    else if (obj is MethodDefinition)
                        currentRef = TypeRef.Create(((MethodDefinition)obj).ReturnType);
                    else if (obj is PropertyDefinition && ((PropertyDefinition)obj).HasParameters)
                        currentRef = TypeRef.Create(((PropertyDefinition)obj).PropertyType);
                    else if (obj is MethodInfo)
                        currentRef = TypeRef.Create(((MethodInfo)obj).ReturnType);
                    else if (obj is PropertyInfo && PropertyInfoUtil.IsIndexed((PropertyInfo)obj))
                        currentRef = TypeRef.Create(((PropertyInfo)obj).PropertyType);
                    if (currentRef == null)
                    {
                        typeRefBase = null;
                        break;
                    }
                    currentRef = currentRef.EvaluateTypeArgumentTypes(parentExpression);
                    if (typeRefBase == null)
                        typeRefBase = currentRef;
                    else if (!currentRef.IsSameRef(typeRefBase))
                    {
                        typeRefBase = null;
                        break;
                    }
                }
            }
            return typeRefBase;
        }

        /// <summary>
        /// Find a type argument for the specified type parameter.
        /// </summary>
        public override TypeRefBase FindTypeArgument(TypeParameterRef typeParameterRef, CodeObject originatingChild)
        {
            // It doesn't make any sense to search for a type parameter in an UnresolvedRef, since it has no
            // definition.  Instead, we must lookup by name and index using the method below.

            // However, we should still recursively search any nested type arguments, since they might be resolved
            return (HasTypeArguments ? FindNestedTypeArgument(typeParameterRef) : null);
        }

        /// <summary>
        /// Find a type argument for the specified type name and type argument index.
        /// </summary>
        public TypeRefBase FindTypeArgument(string name, int index)
        {
            if (HasTypeArguments)
            {
                if ((string)_reference == name && index < TypeArgumentCount)
                {
                    Expression typeArgument = TypeArguments[index];
                    if (typeArgument != null)
                        return typeArgument.EvaluateType();
                }
            }
            return null;
        }

        /// <summary>
        /// Determine if the UnresolvedRef is implicitly convertible to the specified TypeRefBase.
        /// </summary>
        /// <param name="toTypeRefBase">The TypeRef, MethodRef, or UnresolvedRef being checked.</param>
        /// <param name="standardConversionsOnly">True if only standard conversions should be allowed.</param>
        public override bool IsImplicitlyConvertibleTo(TypeRefBase toTypeRefBase, bool standardConversionsOnly)
        {
            // Allow unresolved symbolic references to match if identical
            if (IsSameRef(toTypeRefBase))
                return true;

            // Method Groups: A "method group" represents an overloaded method - multiple methods with the same
            // name, but different parameters.  They don't have a type, but they may be implicitly converted to
            // a delegate.  Method groups come into play only for method invocations, delegate initializations,
            // and method arguments when the parameter has a delegate type (including delegate creation expressions).
            // Method invocations/arguments includes Call, Index, and NewObject.

            // Method groups are represented by an UnresolvedRef with method objects that didn't match completely
            // (usually multiple, although a single one is possible).  Method invocations are resolved by matching
            // the parameter types, and delegate initializations are resolved by matching the type of the delegate,
            // thus avoiding the use of method groups in many cases.  However, when a method name is passed as an
            // argument, it usually can't be resolved immediately, because the method being called won't have been
            // resolved yet (arguments are resolved first so that the parameter matching can work).  Instead, when
            // the method is being resolved, it must detect the unresolved method group argument, and check if any
            // of the possible methods match.  If so, then that method is a match, and if a single matching method
            // is found, the method group argument can then be resolved.
            if (IsMethodGroup)
            {
                // Check if any of the methods in the group is a match
                foreach (MatchCandidate methodMatch in _matches)
                {
                    object methodObj = methodMatch.Object;
                    MethodRef methodRef = null;
                    if (methodObj is MethodDeclBase)
                    {
                        if (methodObj is GenericMethodDecl)
                            methodRef = ((GenericMethodDecl)methodObj).CreateRef(TypeArguments);
                        else
                            methodRef = (MethodRef)((MethodDeclBase)methodObj).CreateRef();
                    }
                    else if (methodObj is MethodDefinition)
                        methodRef = new MethodRef((MethodDefinition)methodObj, TypeArguments);
                    else if (methodObj is MethodInfo)
                        methodRef = new MethodRef((MethodInfo)methodObj, TypeArguments);
                    if (methodRef != null)
                    {
                        methodRef = (MethodRef)methodRef.EvaluateTypeArgumentTypes(_parent);
                        toTypeRefBase = toTypeRefBase.EvaluateTypeArgumentTypes(_parent);
                        // For implicit conversion to work when the method belongs to a generic type, we must set the
                        // parent, which will also be used to determine the originating child (this UnresolvedRef).
                        methodRef.Parent = _parent;
                        if (methodRef.IsImplicitlyConvertibleTo(toTypeRefBase))
                            return true;
                    }
                }
            }

            // If we can determine a minimum common type from all possible matches, then check if that
            // will work (so we can avoid the propagation of errors due to the unresolved type).
            TypeRefBase minimumType = EvaluateToMinimumType();
            if (!(minimumType is UnresolvedRef))
                return minimumType.IsImplicitlyConvertibleTo(toTypeRefBase);

            // Allow an unresolved type reference to be passed or assigned to an 'object' type
            if (toTypeRefBase.IsSameRef(TypeRef.ObjectRef))
                return true;

            return false;
        }

        /// <summary>
        /// Determine if any mismatch was solely due to unresolved or undetermined parameter or argument types.
        /// </summary>
        /// <remarks>
        /// This method is used to display an error as a warning if any mismatch was solely due to unresolved
        /// types - assuming that it might have been valid if all types had been resolved, and therefore assuming
        /// this unresolved reference is secondary to another one.  This does not take overload resolution into
        /// account, so it's actually possible the warning could become an error once the types are resolved, but
        /// this is still the desired behavior as it draws attention to the more important errors first.
        /// </remarks>
        public bool IsAnyMismatchDueToUnresolvedOnly()
        {
            // Check if any candidate failed only due to unresolved references (as far as we can tell)
            return (_matches != null && Enumerable.Any(_matches, delegate(MatchCandidate candidate) { return candidate.IsMismatchDueToUnresolvedOnly(); }));
        }

        /// <summary>
        /// Attach the specified text to the <see cref="UnresolvedRef"/> as a <see cref="Message"/>.
        /// </summary>
        public override void AttachMessage(string text, MessageSeverity messageType, MessageSource messageSource)
        {
            if (messageType != MessageSeverity.Unspecified)
                base.AttachMessage("'" + (string)_reference + "' - " + text, messageType, messageSource);
        }

        #endregion

        #region /* RENDERING */

        public override void AsTextExpression(CodeWriter writer, RenderFlags flags)
        {
            UpdateLineCol(writer, flags);
            writer.Write((string)_reference);
            AsTextTypeArguments(writer, _typeArguments, flags);
            AsTextArrayRanks(writer, flags);
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

Share

About the Author

KenBeckett
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150603.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid