File: DiagnosticAnalyzer\SuppressMessageAttributeState.TargetSymbolResolver.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    internal partial class SuppressMessageAttributeState
    {
        private const string s_suppressionPrefix = "~";
 
        [StructLayout(LayoutKind.Auto)]
        private struct TargetSymbolResolver
        {
            private static readonly char[] s_nameDelimiters = { ':', '.', '+', '(', ')', '<', '>', '[', ']', '{', '}', ',', '&', '*', '`' };
            private static readonly string[] s_callingConventionStrings =
            {
                "[vararg]",
                "[cdecl]",
                "[fastcall]",
                "[stdcall]",
                "[thiscall]"
            };
 
            private static readonly ParameterInfo[] s_noParameters = Array.Empty<ParameterInfo>();
 
            private readonly Compilation _compilation;
            private readonly TargetScope _scope;
            private readonly string _name;
            private int _index;
 
            public TargetSymbolResolver(Compilation compilation, TargetScope scope, string fullyQualifiedName)
            {
                _compilation = compilation;
                _scope = scope;
                _name = fullyQualifiedName;
                _index = 0;
            }
 
            private static string RemovePrefix(string id, string prefix)
            {
                if (id != null && prefix != null && id.StartsWith(prefix, StringComparison.Ordinal))
                {
                    return id[prefix.Length..];
                }
 
                return id;
            }
 
            /// <summary>
            /// Attempts to resolve the "Target" argument of the global SuppressMessageAttribute to symbols in compilation.
            /// </summary>
            /// <param name="resolvedWithDocCommentIdFormat">Indicates if resolved "Target" argument is in Roslyn's <see cref="DocumentationCommentId"/> format.</param>
            /// <returns>Resolved symbols for the the "Target" argument of the global SuppressMessageAttribute.</returns>
            public ImmutableArray<ISymbol> Resolve(out bool resolvedWithDocCommentIdFormat)
            {
                resolvedWithDocCommentIdFormat = false;
                if (string.IsNullOrEmpty(_name))
                {
                    return ImmutableArray<ISymbol>.Empty;
                }
 
                // Try to parse the name as declaration ID generated from symbol's documentation comment Id.
                var nameWithoutPrefix = RemovePrefix(_name, s_suppressionPrefix);
                var docIdResults = DocumentationCommentId.GetSymbolsForDeclarationId(nameWithoutPrefix, _compilation);
                if (docIdResults.Length > 0)
                {
                    resolvedWithDocCommentIdFormat = true;
                    return docIdResults;
                }
 
                var results = ArrayBuilder<ISymbol>.GetInstance();
 
                // Parse 'e:' prefix used by FxCop to differentiate between event and non-event symbols of the same name.
                bool isEvent = false;
                if (_name.Length >= 2 && _name[0] == 'e' && _name[1] == ':')
                {
                    isEvent = true;
                    _index = 2;
                }
 
                INamespaceOrTypeSymbol containingSymbol = _compilation.GlobalNamespace;
                bool? segmentIsNamedTypeName = null;
 
                while (true)
                {
                    var segment = ParseNextNameSegment();
 
                    // Special case: Roslyn names indexers "this[]" in CSharp, FxCop names them "Item" with parameters in [] brackets
                    bool isIndexerProperty = false;
                    if (segment == "Item" && PeekNextChar() == '[')
                    {
                        isIndexerProperty = true;
                        if (_compilation.Language == LanguageNames.CSharp)
                        {
                            segment = "this[]";
                        }
                    }
 
                    var candidateMembers = containingSymbol.GetMembers(segment);
                    if (candidateMembers.Length == 0)
                    {
                        break;
                    }
 
                    if (segmentIsNamedTypeName.HasValue)
                    {
                        candidateMembers = segmentIsNamedTypeName.Value ?
                            candidateMembers.Where(s => s.Kind == SymbolKind.NamedType).ToImmutableArray() :
                            candidateMembers.Where(s => s.Kind != SymbolKind.NamedType).ToImmutableArray();
 
                        segmentIsNamedTypeName = null;
                    }
 
                    int? arity = null;
                    ParameterInfo[] parameters = null;
 
                    // Check for generic arity
                    if (_scope != TargetScope.Namespace && PeekNextChar() == '`')
                    {
                        ++_index;
                        arity = ReadNextInteger();
                    }
 
                    // Check for method or indexer parameter list
                    var nextChar = PeekNextChar();
 
                    if (!isIndexerProperty && nextChar == '(' || isIndexerProperty && nextChar == '[')
                    {
                        parameters = ParseParameterList();
                        if (parameters == null)
                        {
                            // Failed to resolve parameter list
                            break;
                        }
                    }
                    else if (nextChar is '.' or '+')
                    {
                        ++_index;
 
                        if (arity > 0 || nextChar == '+')
                        {
                            // The name continues and either has an arity or specifically continues with a '+'
                            // so segment must be the name of a named type
                            containingSymbol = GetFirstMatchingNamedType(candidateMembers, arity ?? 0);
                        }
                        else
                        {
                            // The name continues with a '.' and does not specify a generic arity
                            // so segment must be the name of a namespace or a named type
                            containingSymbol = GetFirstMatchingNamespaceOrType(candidateMembers);
                        }
 
                        if (containingSymbol == null)
                        {
                            // If we cannot resolve the name on the left of the delimiter, we have no 
                            // hope of finding the symbol.
                            break;
                        }
 
                        if (containingSymbol.Kind == SymbolKind.NamedType)
                        {
                            // If segment resolves to a named type, that restricts what the next segment
                            // can resolve to depending on whether the name continues with '+' or '.'
                            segmentIsNamedTypeName = nextChar == '+';
                        }
 
                        continue;
                    }
 
                    if (_scope == TargetScope.Member && !isIndexerProperty && parameters != null)
                    {
                        TypeInfo? returnType = null;
                        if (PeekNextChar() == ':')
                        {
                            ++_index;
                            returnType = ParseNamedType(null);
                        }
 
                        foreach (var method in GetMatchingMethods(candidateMembers, arity, parameters, returnType))
                        {
                            results.Add(method);
                        }
 
                        break;
                    }
 
                    ISymbol singleResult;
 
                    switch (_scope)
                    {
                        case TargetScope.Namespace:
                            singleResult = candidateMembers.FirstOrDefault(s => s.Kind == SymbolKind.Namespace);
                            break;
 
                        case TargetScope.Type:
                            singleResult = GetFirstMatchingNamedType(candidateMembers, arity ?? 0);
                            break;
 
                        case TargetScope.Member:
                            if (isIndexerProperty)
                            {
                                singleResult = GetFirstMatchingIndexer(candidateMembers, parameters);
                            }
                            else if (isEvent)
                            {
                                singleResult = candidateMembers.FirstOrDefault(s => s.Kind == SymbolKind.Event);
                            }
                            else
                            {
                                singleResult = candidateMembers.FirstOrDefault(s =>
                                    s.Kind is not SymbolKind.Namespace and
                                    not SymbolKind.NamedType);
                            }
                            break;
 
                        default:
                            throw ExceptionUtilities.UnexpectedValue(_scope);
                    }
 
                    if (singleResult != null)
                    {
                        results.Add(singleResult);
                    }
 
                    break;
                }
 
                return results.ToImmutableAndFree();
            }
 
            private string ParseNextNameSegment()
            {
                // Ignore optional octothorpe in the member name used by FxCop to differentiate between
                // Orcas and Whidbey name providers. The fully-qualified member name format generated by each of
                // these name providers is similar enough that we can just ignore this character.
                if (PeekNextChar() == '#')
                {
                    ++_index;
 
                    // Ignore calling convention strings generated by FxCop for methods.
                    // Methods can't differ solely by calling convention in C# or VB.
                    if (PeekNextChar() == '[')
                    {
                        foreach (string callingConvention in s_callingConventionStrings)
                        {
                            if (callingConvention == _name.Substring(_index, callingConvention.Length))
                            {
                                _index += callingConvention.Length;
                                break;
                            }
                        }
                    }
                }
 
                string segment;
 
                // Find the end of the next name segment, special case constructors which start with '.'
                int delimiterOffset = PeekNextChar() == '.' ?
                    _name.IndexOfAny(s_nameDelimiters, _index + 1) :
                    _name.IndexOfAny(s_nameDelimiters, _index);
 
                if (delimiterOffset >= 0)
                {
                    segment = _name[_index..delimiterOffset];
                    _index = delimiterOffset;
                }
                else
                {
                    segment = _name[_index..];
                    _index = _name.Length;
                }
 
                return segment;
            }
 
            private char PeekNextChar()
            {
                return _index >= _name.Length ? '\0' : _name[_index];
            }
 
            private int ReadNextInteger()
            {
                int n = 0;
 
                while (_index < _name.Length && char.IsDigit(_name[_index]))
                {
                    n = n * 10 + (_name[_index] - '0');
                    ++_index;
                }
 
                return n;
            }
 
            private ParameterInfo[] ParseParameterList()
            {
                // Consume the opening parenthesis or bracket
                Debug.Assert(PeekNextChar() is '(' or '[');
                ++_index;
 
                var nextChar = PeekNextChar();
                if (nextChar is ')' or ']')
                {
                    // Empty parameter list
                    ++_index;
                    return s_noParameters;
                }
 
                var builder = new ArrayBuilder<ParameterInfo>();
 
                while (true)
                {
                    var parameter = ParseParameter();
                    if (parameter != null)
                    {
                        builder.Add(parameter.Value);
                    }
                    else
                    {
                        builder.Free();
                        return null;
                    }
 
                    if (PeekNextChar() == ',')
                    {
                        ++_index;
                    }
                    else
                    {
                        break;
                    }
                }
 
                nextChar = PeekNextChar();
                if (nextChar is ')' or ']')
                {
                    // Consume the closing parenthesis or bracket
                    ++_index;
                }
                else
                {
                    // Malformed parameter list: missing close parenthesis or bracket
                    builder.Free();
                    return null;
                }
 
                return builder.ToArrayAndFree();
            }
 
            private ParameterInfo? ParseParameter()
            {
                var type = ParseType(null);
                if (type == null)
                {
                    return null;
                }
 
                var isRefOrOut = PeekNextChar() == '&';
                if (isRefOrOut)
                {
                    ++_index;
                }
 
                return new ParameterInfo(type.Value, isRefOrOut);
            }
 
            private TypeInfo? ParseType(ISymbol bindingContext)
            {
                TypeInfo? result;
 
                IgnoreCustomModifierList();
 
                if (PeekNextChar() == '!')
                {
                    result = ParseIndexedTypeParameter(bindingContext);
                }
                else
                {
                    result = ParseNamedType(bindingContext);
 
                    // If parsing as a named type failed, this could be a named type parameter,
                    // which we will only be able to resolve once we have a binding context.
                    if (bindingContext != null && result.HasValue && !result.Value.IsBound)
                    {
                        _index = result.Value.StartIndex;
                        result = ParseNamedTypeParameter(bindingContext);
                    }
                }
 
                if (result == null)
                {
                    return null;
                }
 
                if (result.Value.IsBound)
                {
                    var typeSymbol = result.Value.Type;
 
                    // Handle pointer and array specifiers for bound types
                    while (true)
                    {
                        IgnoreCustomModifierList();
 
                        var nextChar = PeekNextChar();
                        if (nextChar == '[')
                        {
                            typeSymbol = ParseArrayType(typeSymbol);
                            if (typeSymbol == null)
                            {
                                return null;
                            }
                            continue;
                        }
 
                        if (nextChar == '*')
                        {
                            ++_index;
                            typeSymbol = _compilation.CreatePointerTypeSymbol(typeSymbol);
                            continue;
                        }
 
                        break;
                    }
 
                    return TypeInfo.Create(typeSymbol);
                }
 
                // Skip pointer and array specifiers for unbound types
                IgnorePointerAndArraySpecifiers();
                return result;
            }
 
            private void IgnoreCustomModifierList()
            {
                // NOTE: There is currently no way to create symbols
                // with custom modifiers from outside the compiler layer. In
                // particular, there is no language agnostic way to attach custom
                // modifiers to symbols. As a result we cannot match symbols which
                // have custom modifiers, because their public equals overrides in
                // general explicitly check custom modifiers. So we just ignore
                // custom modifier lists. This would only matter in the case that
                // someone targeted a SuppressMessageAttribute at a method that
                // overloads a method from metadata which uses custom modifiers.
                if (PeekNextChar() == '{')
                {
                    for (; _index < _name.Length && _name[_index] != '}'; ++_index) { }
                }
            }
 
            private void IgnorePointerAndArraySpecifiers()
            {
                bool inBrackets = false;
                for (; _index < _name.Length; ++_index)
                {
                    switch (PeekNextChar())
                    {
                        case '[':
                            inBrackets = true;
                            break;
                        case ']':
                            if (!inBrackets)
                            {
                                // End of indexer parameter list
                                return;
                            }
                            inBrackets = false;
                            break;
                        case '*':
                            break;
                        default:
                            if (!inBrackets)
                            {
                                // End of parameter type name
                                return;
                            }
                            break;
                    }
                }
            }
 
            private TypeInfo? ParseIndexedTypeParameter(ISymbol bindingContext)
            {
                var startIndex = _index;
 
                Debug.Assert(PeekNextChar() == '!');
                ++_index;
 
                if (PeekNextChar() == '!')
                {
                    // !! means this is a method type parameter
                    ++_index;
                    var methodTypeParameterIndex = ReadNextInteger();
 
                    var methodContext = bindingContext as IMethodSymbol;
                    if (methodContext != null)
                    {
                        var count = methodContext.TypeParameters.Length;
                        if (count > 0 && methodTypeParameterIndex < count)
                        {
                            return TypeInfo.Create(methodContext.TypeParameters[methodTypeParameterIndex]);
                        }
 
                        // No such parameter
                        return null;
                    }
 
                    // If there is no method context, then the type is unbound and must be bound later
                    return TypeInfo.CreateUnbound(startIndex);
                }
 
                // ! means this is a regular type parameter
                var typeParameterIndex = ReadNextInteger();
 
                if (bindingContext != null)
                {
                    var typeParameter = GetNthTypeParameter(bindingContext.ContainingType, typeParameterIndex);
                    if (typeParameter != null)
                    {
                        return TypeInfo.Create(typeParameter);
                    }
 
                    // no such parameter
                    return null;
                }
 
                // If there is no binding context, then the type is unbound and must be bound later
                return TypeInfo.CreateUnbound(startIndex);
            }
 
            private TypeInfo? ParseNamedTypeParameter(ISymbol bindingContext)
            {
                Debug.Assert(bindingContext != null);
 
                var typeParameterName = ParseNextNameSegment();
 
                var methodContext = bindingContext as IMethodSymbol;
                if (methodContext != null)
                {
                    // Check this method's type parameters for a name that matches
                    for (int i = 0; i < methodContext.TypeParameters.Length; ++i)
                    {
                        if (methodContext.TypeParameters[i].Name == typeParameterName)
                        {
                            return TypeInfo.Create(methodContext.TypeArguments[i]);
                        }
                    }
                }
 
                // Walk up the symbol tree until we find a type parameter with a name that matches
                for (var containingType = bindingContext.ContainingType; containingType != null; containingType = containingType.ContainingType)
                {
                    for (int i = 0; i < containingType.TypeParameters.Length; ++i)
                    {
                        if (containingType.TypeParameters[i].Name == typeParameterName)
                        {
                            return TypeInfo.Create(containingType.TypeArguments[i]);
                        }
                    }
                }
 
                return null;
            }
 
            private TypeInfo? ParseNamedType(ISymbol bindingContext)
            {
                INamespaceOrTypeSymbol containingSymbol = _compilation.GlobalNamespace;
 
                int startIndex = _index;
 
                while (true)
                {
                    var segment = ParseNextNameSegment();
                    var candidateMembers = containingSymbol.GetMembers(segment);
                    if (candidateMembers.Length == 0)
                    {
                        return TypeInfo.CreateUnbound(startIndex);
                    }
 
                    int arity = 0;
                    TypeInfo[] typeArguments = null;
 
                    // Check for generic arity
                    if (PeekNextChar() == '`')
                    {
                        ++_index;
                        arity = ReadNextInteger();
                    }
 
                    // Check for type argument list
                    if (PeekNextChar() == '<')
                    {
                        typeArguments = ParseTypeArgumentList(bindingContext);
                        if (typeArguments == null)
                        {
                            return null;
                        }
 
                        if (typeArguments.Any(a => !a.IsBound))
                        {
                            return TypeInfo.CreateUnbound(startIndex);
                        }
                    }
 
                    var nextChar = PeekNextChar();
                    if (nextChar is '.' or '+')
                    {
                        ++_index;
 
                        if (arity > 0 || nextChar == '+')
                        {
                            // Segment is the name of a named type since the name has an arity or continues with a '+'
                            containingSymbol = GetFirstMatchingNamedType(candidateMembers, arity);
                        }
                        else
                        {
                            // Segment is the name of a namespace or type because the name continues with a '.'
                            containingSymbol = GetFirstMatchingNamespaceOrType(candidateMembers);
                        }
 
                        if (containingSymbol == null)
                        {
                            // If we cannot resolve the name on the left of the delimiter, we have no 
                            // hope of finding the symbol.
                            return null;
                        }
 
                        continue;
                    }
 
                    INamedTypeSymbol typeSymbol = GetFirstMatchingNamedType(candidateMembers, arity);
                    if (typeSymbol == null)
                    {
                        return null;
                    }
 
                    if (typeArguments != null)
                    {
                        typeSymbol = typeSymbol.Construct(typeArguments.Select(t => t.Type).ToArray());
                    }
 
                    return TypeInfo.Create(typeSymbol);
                }
            }
 
            private TypeInfo[] ParseTypeArgumentList(ISymbol bindingContext)
            {
                Debug.Assert(PeekNextChar() == '<');
                ++_index;
 
                var builder = new ArrayBuilder<TypeInfo>();
 
                while (true)
                {
                    var type = ParseType(bindingContext);
                    if (type == null)
                    {
                        builder.Free();
                        return null;
                    }
 
                    builder.Add(type.Value);
 
                    if (PeekNextChar() == ',')
                    {
                        ++_index;
                    }
                    else
                    {
                        break;
                    }
                }
 
                if (PeekNextChar() == '>')
                {
                    ++_index;
                }
                else
                {
                    builder.Free();
                    return null;
                }
 
                return builder.ToArrayAndFree();
            }
 
            private ITypeSymbol ParseArrayType(ITypeSymbol typeSymbol)
            {
                Debug.Assert(PeekNextChar() == '[');
                ++_index;
                int rank = 1;
 
                while (true)
                {
                    var nextChar = PeekNextChar();
                    if (nextChar == ',')
                    {
                        ++rank;
                    }
                    else if (nextChar == ']')
                    {
                        ++_index;
                        return _compilation.CreateArrayTypeSymbol(typeSymbol, rank);
                    }
                    else if (!char.IsDigit(nextChar) && nextChar != '.')
                    {
                        // Malformed array type specifier: invalid character
                        return null;
                    }
 
                    ++_index;
                }
            }
 
            private ISymbol GetFirstMatchingIndexer(ImmutableArray<ISymbol> candidateMembers, ParameterInfo[] parameters)
            {
                foreach (var symbol in candidateMembers)
                {
                    var propertySymbol = symbol as IPropertySymbol;
                    if (propertySymbol != null && AllParametersMatch(propertySymbol.Parameters, parameters))
                    {
                        return propertySymbol;
                    }
                }
 
                return null;
            }
 
            private ImmutableArray<IMethodSymbol> GetMatchingMethods(ImmutableArray<ISymbol> candidateMembers, int? arity, ParameterInfo[] parameters, TypeInfo? returnType)
            {
                var builder = new ArrayBuilder<IMethodSymbol>();
 
                foreach (var symbol in candidateMembers)
                {
                    var methodSymbol = symbol as IMethodSymbol;
                    if (methodSymbol == null ||
                        (arity != null && methodSymbol.Arity != arity))
                    {
                        continue;
                    }
 
                    if (!AllParametersMatch(methodSymbol.Parameters, parameters))
                    {
                        continue;
                    }
 
                    if (returnType == null)
                    {
                        // If no return type specified, then any matches
                        builder.Add(methodSymbol);
                    }
                    else
                    {
                        // If return type is specified, then it must match
                        var boundReturnType = BindParameterOrReturnType(methodSymbol, returnType.Value);
                        if (boundReturnType != null && methodSymbol.ReturnType.Equals(boundReturnType))
                        {
                            builder.Add(methodSymbol);
                        }
                    }
                }
 
                return builder.ToImmutableAndFree();
            }
 
            private bool AllParametersMatch(ImmutableArray<IParameterSymbol> symbolParameters, ParameterInfo[] expectedParameters)
            {
                if (symbolParameters.Length != expectedParameters.Length)
                {
                    return false;
                }
 
                for (int i = 0; i < expectedParameters.Length; ++i)
                {
                    if (!ParameterMatches(symbolParameters[i], expectedParameters[i]))
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            private bool ParameterMatches(IParameterSymbol symbol, ParameterInfo parameterInfo)
            {
                // same ref'ness?
                if ((symbol.RefKind == RefKind.None) == parameterInfo.IsRefOrOut)
                {
                    return false;
                }
 
                var parameterType = BindParameterOrReturnType(symbol.ContainingSymbol, parameterInfo.Type);
 
                return parameterType != null && symbol.Type.Equals(parameterType);
            }
 
            private ITypeSymbol BindParameterOrReturnType(ISymbol bindingContext, TypeInfo type)
            {
                if (type.IsBound)
                {
                    return type.Type;
                }
 
                var currentIndex = _index;
                _index = type.StartIndex;
                var result = this.ParseType(bindingContext);
                _index = currentIndex;
 
                return result?.Type;
            }
 
            private static INamedTypeSymbol GetFirstMatchingNamedType(ImmutableArray<ISymbol> candidateMembers, int arity)
            {
                return (INamedTypeSymbol)candidateMembers.FirstOrDefault(s =>
                    s.Kind == SymbolKind.NamedType &&
                    ((INamedTypeSymbol)s).Arity == arity);
            }
 
            private static INamespaceOrTypeSymbol GetFirstMatchingNamespaceOrType(ImmutableArray<ISymbol> candidateMembers)
            {
                return (INamespaceOrTypeSymbol)candidateMembers
                    .FirstOrDefault(s =>
                        s.Kind is SymbolKind.Namespace or
                        SymbolKind.NamedType);
            }
 
            private static ITypeParameterSymbol GetNthTypeParameter(INamedTypeSymbol typeSymbol, int n)
            {
                var containingTypeParameterCount = GetTypeParameterCount(typeSymbol.ContainingType);
                if (n < containingTypeParameterCount)
                {
                    return GetNthTypeParameter(typeSymbol.ContainingType, n);
                }
 
                var index = n - containingTypeParameterCount;
                var typeParameters = typeSymbol.TypeParameters;
                if (index < typeParameters.Length)
                {
                    return typeParameters[index];
                }
 
                return null;
            }
 
            private static int GetTypeParameterCount(INamedTypeSymbol typeSymbol)
            {
                if (typeSymbol == null)
                {
                    return 0;
                }
 
                return typeSymbol.TypeParameters.Length + GetTypeParameterCount(typeSymbol.ContainingType);
            }
 
            [StructLayout(LayoutKind.Auto)]
            private readonly struct TypeInfo
            {
                // The type, may be null if unbound.
                public readonly ITypeSymbol Type;
 
                // The start index into this.name for parsing this type if the type is not known
                // This index is used when rebinding later when the method context is known
                public readonly int StartIndex;
 
                public bool IsBound => this.Type != null;
 
                private TypeInfo(ITypeSymbol type, int startIndex)
                {
                    this.Type = type;
                    this.StartIndex = startIndex;
                }
 
                public static TypeInfo Create(ITypeSymbol type)
                {
                    Debug.Assert(type != null);
                    return new TypeInfo(type, -1);
                }
 
                public static TypeInfo CreateUnbound(int startIndex)
                {
                    Debug.Assert(startIndex >= 0);
                    return new TypeInfo(null, startIndex);
                }
            }
 
            [StructLayout(LayoutKind.Auto)]
            private readonly struct ParameterInfo
            {
                public readonly TypeInfo Type;
                public readonly bool IsRefOrOut;
 
                public ParameterInfo(TypeInfo type, bool isRefOrOut)
                {
                    this.Type = type;
                    this.IsRefOrOut = isRefOrOut;
                }
            }
        }
    }
}