|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CSharp.RuntimeBinder.Errors;
using Microsoft.CSharp.RuntimeBinder.Syntax;
namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
// ----------------------------------------------------------------------------
// This class takes an EXPRMEMGRP and a set of arguments and binds the arguments
// to the best applicable method in the group.
// ----------------------------------------------------------------------------
internal readonly partial struct ExpressionBinder
{
internal sealed class GroupToArgsBinder
{
private enum Result
{
Success,
Failure_SearchForExpanded,
Failure_NoSearchForExpanded
}
private readonly ExpressionBinder _pExprBinder;
private bool _fCandidatesUnsupported;
private readonly BindingFlag _fBindFlags;
private readonly ExprMemberGroup _pGroup;
private readonly ArgInfos _pArguments;
private readonly ArgInfos _pOriginalArguments;
private readonly NamedArgumentsKind _namedArgumentsKind;
private AggregateType _pCurrentType;
private MethodOrPropertySymbol _pCurrentSym;
private TypeArray _pCurrentTypeArgs;
private TypeArray _pCurrentParameters;
private int _nArgBest;
// end of current namespaces extension method list
private readonly GroupToArgsBinderResult _results;
private readonly List<CandidateFunctionMember> _methList;
private readonly MethPropWithInst _mpwiParamTypeConstraints;
private readonly MethPropWithInst _mpwiBogus;
private readonly MethPropWithInst _misnamed;
private readonly MethPropWithInst _mpwiCantInferInstArg;
private readonly MethWithType _mwtBadArity;
private Name _pInvalidSpecifiedName;
private Name _pNameUsedInPositionalArgument;
private Name _pDuplicateSpecifiedName;
// When we find a type with an interface, then we want to mark all other interfaces that it
// implements as being hidden. We also want to mark object as being hidden. So stick them
// all in this list, and then for subsequent types, if they're in this list, then we
// ignore them.
private readonly List<CType> _HiddenTypes;
private bool _bArgumentsChangedForNamedOrOptionalArguments;
public GroupToArgsBinder(ExpressionBinder exprBinder, BindingFlag bindFlags, ExprMemberGroup grp, ArgInfos args, ArgInfos originalArgs, NamedArgumentsKind namedArgumentsKind)
{
Debug.Assert(grp != null);
Debug.Assert(args != null);
_pExprBinder = exprBinder;
_fCandidatesUnsupported = false;
_fBindFlags = bindFlags;
_pGroup = grp;
_pArguments = args;
_pOriginalArguments = originalArgs;
_namedArgumentsKind = namedArgumentsKind;
_pCurrentType = null;
_pCurrentSym = null;
_pCurrentTypeArgs = null;
_pCurrentParameters = null;
_nArgBest = -1;
_results = new GroupToArgsBinderResult();
_methList = new List<CandidateFunctionMember>();
_mpwiParamTypeConstraints = new MethPropWithInst();
_mpwiBogus = new MethPropWithInst();
_misnamed = new MethPropWithInst();
_mpwiCantInferInstArg = new MethPropWithInst();
_mwtBadArity = new MethWithType();
_HiddenTypes = new List<CType>();
}
// ----------------------------------------------------------------------------
// This method does the actual binding.
// ----------------------------------------------------------------------------
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
public void Bind()
{
Debug.Assert(_pGroup.SymKind == SYMKIND.SK_MethodSymbol || _pGroup.SymKind == SYMKIND.SK_PropertySymbol && 0 != (_pGroup.Flags & EXPRFLAG.EXF_INDEXER));
LookForCandidates();
if (!GetResultOfBind())
{
throw ReportErrorsOnFailure();
}
}
public GroupToArgsBinderResult GetResultsOfBind() => _results;
private static CType GetTypeQualifier(ExprMemberGroup pGroup)
{
Debug.Assert(pGroup != null);
return (pGroup.Flags & EXPRFLAG.EXF_CTOR) != 0 ? pGroup.ParentType : pGroup.OptionalObject?.Type;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void LookForCandidates()
{
bool fExpanded = false;
bool bSearchForExpanded = true;
bool allCandidatesUnsupported = true;
bool lookedAtCandidates = false;
// Calculate the mask based on the type of the sym we've found so far. This
// is to ensure that if we found a propsym (or methsym, or whatever) the
// iterator will only return propsyms (or methsyms, or whatever)
symbmask_t mask = (symbmask_t)(1 << (int)_pGroup.SymKind);
CMemberLookupResults.CMethodIterator iterator = _pGroup.MemberLookupResults.GetMethodIterator(GetTypeQualifier(_pGroup), _pExprBinder.ContextForMemberLookup, _pGroup.TypeArgs.Count, _pGroup.Flags, mask, _namedArgumentsKind == NamedArgumentsKind.NonTrailing ? _pOriginalArguments : null);
while (true)
{
bool bFoundExpanded;
bFoundExpanded = false;
if (bSearchForExpanded && !fExpanded)
{
bFoundExpanded = fExpanded = ConstructExpandedParameters();
}
// Get the next sym to search for.
if (!bFoundExpanded)
{
fExpanded = false;
if (!GetNextSym(iterator))
{
break;
}
// Get the parameters.
_pCurrentParameters = _pCurrentSym.Params;
bSearchForExpanded = true;
}
if (_bArgumentsChangedForNamedOrOptionalArguments)
{
// If we changed them last time, then we need to reset them.
_bArgumentsChangedForNamedOrOptionalArguments = false;
CopyArgInfos(_pOriginalArguments, _pArguments);
}
// If we have named arguments, reorder them for this method.
// If we don't have Exprs, its because we're doing a method group conversion.
// In those scenarios, we never want to add named arguments or optional arguments.
if (_namedArgumentsKind == NamedArgumentsKind.Positioning)
{
if (!ReOrderArgsForNamedArguments())
{
continue;
}
}
else if (HasOptionalParameters())
{
if (!AddArgumentsForOptionalParameters())
{
continue;
}
}
if (!bFoundExpanded)
{
lookedAtCandidates = true;
allCandidatesUnsupported &= CSemanticChecker.CheckBogus(_pCurrentSym);
// If we have the wrong number of arguments and still have room in our cache of 20,
// then store it in our cache and go to the next sym.
if (_pCurrentParameters.Count != _pArguments.carg)
{
bSearchForExpanded = true;
continue;
}
}
// If we cant use the current symbol, then we've filtered it, so get the next one.
if (!iterator.CanUseCurrentSymbol)
{
continue;
}
// Get the current type args.
Result currentTypeArgsResult = DetermineCurrentTypeArgs();
if (currentTypeArgsResult != Result.Success)
{
bSearchForExpanded = (currentTypeArgsResult == Result.Failure_SearchForExpanded);
continue;
}
// Check access.
bool fCanAccess = !iterator.IsCurrentSymbolInaccessible;
if (!fCanAccess && (!_methList.IsEmpty() || _results.InaccessibleResult))
{
// We'll never use this one for error reporting anyway, so just skip it.
bSearchForExpanded = false;
continue;
}
// Check misnamed.
bool misnamed = fCanAccess && iterator.IsCurrentSymbolMisnamed;
if (misnamed && (!_methList.IsEmpty() || _results.InaccessibleResult || _misnamed))
{
bSearchForExpanded = false;
continue;
}
// Check bogus.
bool fBogus = fCanAccess && !misnamed && iterator.IsCurrentSymbolBogus;
if (fBogus && (!_methList.IsEmpty() || _results.InaccessibleResult || _mpwiBogus || _misnamed))
{
// We'll never use this one for error reporting anyway, so just skip it.
bSearchForExpanded = false;
continue;
}
// Check convertibility of arguments.
if (!ArgumentsAreConvertible())
{
bSearchForExpanded = true;
continue;
}
// We know we have the right number of arguments and they are all convertible.
if (!fCanAccess)
{
// In case we never get an accessible method, this will allow us to give
// a better error...
Debug.Assert(!_results.InaccessibleResult);
_results.InaccessibleResult.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
}
else if (misnamed)
{
Debug.Assert(!_misnamed);
_misnamed.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
}
else if (fBogus)
{
// In case we never get a good method, this will allow us to give
// a better error...
Debug.Assert(!_mpwiBogus);
_mpwiBogus.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
}
else
{
// This is a plausible method / property to call.
// Link it in at the end of the list.
_methList.Add(new CandidateFunctionMember(
new MethPropWithInst(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs),
_pCurrentParameters,
0,
fExpanded));
// When we find a method, we check if the type has interfaces. If so, mark the other interfaces
// as hidden, and object as well.
if (_pCurrentType.IsInterfaceType)
{
foreach (AggregateType type in _pCurrentType.IfacesAll.Items)
{
Debug.Assert(type.IsInterfaceType);
_HiddenTypes.Add(type);
}
// Mark object.
AggregateType typeObject = SymbolLoader.GetPredefindType(PredefinedType.PT_OBJECT);
_HiddenTypes.Add(typeObject);
}
}
// Don't look at the expanded form.
bSearchForExpanded = false;
}
_fCandidatesUnsupported = allCandidatesUnsupported && lookedAtCandidates;
// Restore the arguments to their original state if we changed them for named/optional arguments.
// ILGen will take care of putting the real arguments in there.
if (_bArgumentsChangedForNamedOrOptionalArguments)
{
// If we changed them last time, then we need to reset them.
CopyArgInfos(_pOriginalArguments, _pArguments);
}
}
private static void CopyArgInfos(ArgInfos src, ArgInfos dst)
{
dst.carg = src.carg;
dst.types = src.types;
dst.prgexpr.Clear();
for (int i = 0; i < src.prgexpr.Count; i++)
{
dst.prgexpr.Add(src.prgexpr[i]);
}
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private bool GetResultOfBind()
{
// We looked at all the evidence, and we come to render the verdict:
if (!_methList.IsEmpty())
{
CandidateFunctionMember pmethBest;
if (_methList.Count == 1)
{
// We found the single best method to call.
pmethBest = _methList.Head();
}
else
{
// We have some ambiguities, lets sort them out.
CType pTypeThrough = _pGroup.OptionalObject?.Type;
pmethBest = _pExprBinder.FindBestMethod(_methList, pTypeThrough, _pArguments, out CandidateFunctionMember pAmbig1, out CandidateFunctionMember pAmbig2);
if (null == pmethBest)
{
if (pAmbig1.@params != pAmbig2.@params ||
pAmbig1.mpwi.MethProp().Params.Count != pAmbig2.mpwi.MethProp().Params.Count ||
pAmbig1.mpwi.TypeArgs != pAmbig2.mpwi.TypeArgs ||
pAmbig1.mpwi.GetType() != pAmbig2.mpwi.GetType() ||
pAmbig1.mpwi.MethProp().Params == pAmbig2.mpwi.MethProp().Params)
{
throw ErrorHandling.Error(ErrorCode.ERR_AmbigCall, pAmbig1.mpwi, pAmbig2.mpwi);
}
// The two signatures are identical so don't use the type args in the error message.
throw ErrorHandling.Error(ErrorCode.ERR_AmbigCall, pAmbig1.mpwi.MethProp(), pAmbig2.mpwi.MethProp());
}
}
// This is the "success" exit path.
Debug.Assert(pmethBest != null);
_results.BestResult = pmethBest.mpwi;
// Record our best match in the memgroup as well. This is temporary.
ReportErrorsOnSuccess();
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// This method returns true if we're able to match arguments to their names.
// If we either have too many arguments, or we cannot match their names, then
// we return false.
//
// Note that if we have not enough arguments, we still return true as long as
// we can find matching parameters for each named arguments, and all parameters
// that do not have a matching argument are optional parameters.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private bool ReOrderArgsForNamedArguments()
{
// First we need to find the method that we're actually trying to call.
MethodOrPropertySymbol methprop = FindMostDerivedMethod(_pCurrentSym, _pGroup.OptionalObject);
if (methprop == null)
{
return false;
}
int numParameters = _pCurrentParameters.Count;
// If we have no parameters, or fewer parameters than we have arguments, bail.
if (numParameters == 0 || numParameters < _pArguments.carg)
{
return false;
}
// Make sure all the names we specified are in the list and we don't have duplicates.
if (!NamedArgumentNamesAppearInParameterList(methprop))
{
return false;
}
_bArgumentsChangedForNamedOrOptionalArguments = ReOrderArgsForNamedArguments(
methprop, _pCurrentParameters, _pCurrentType, _pGroup, _pArguments);
return _bArgumentsChangedForNamedOrOptionalArguments;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal static bool ReOrderArgsForNamedArguments(
MethodOrPropertySymbol methprop, TypeArray pCurrentParameters, AggregateType pCurrentType, ExprMemberGroup pGroup, ArgInfos pArguments)
{
// We use the param count from pCurrentParameters because they may have been resized
// for param arrays.
int numParameters = pCurrentParameters.Count;
Expr[] pExprArguments = new Expr[numParameters];
// Now go through the parameters. First set all positional arguments in the new argument
// set, then for the remainder, look for a named argument with a matching name.
int index = 0;
Expr paramArrayArgument = null;
TypeArray @params = TypeManager.SubstTypeArray(
pCurrentParameters,
pCurrentType,
pGroup.TypeArgs);
foreach (Name name in methprop.ParameterNames)
{
// This can happen if we had expanded our param array to size 0.
if (index >= pCurrentParameters.Count)
{
break;
}
// If:
// (1) we have a param array method
// (2) we're on the last arg
// (3) the thing we have is an array init thats generated for param array
// then let us through.
if (methprop.isParamArray &&
index < pArguments.carg &&
pArguments.prgexpr[index] is ExprArrayInit arrayInit && arrayInit.GeneratedForParamArray)
{
paramArrayArgument = pArguments.prgexpr[index];
}
// Positional.
if (index < pArguments.carg &&
!(pArguments.prgexpr[index] is ExprNamedArgumentSpecification) &&
!(pArguments.prgexpr[index] is ExprArrayInit arrayInitPos && arrayInitPos.GeneratedForParamArray))
{
pExprArguments[index] = pArguments.prgexpr[index++];
continue;
}
// Look for names.
Expr pNewArg = FindArgumentWithName(pArguments, name);
if (pNewArg == null)
{
if (methprop.IsParameterOptional(index))
{
pNewArg = GenerateOptionalArgument(methprop, @params[index], index);
}
else if (paramArrayArgument != null && index == methprop.Params.Count - 1)
{
// If we have a param array argument and we're on the last one, then use it.
pNewArg = paramArrayArgument;
}
else
{
// No name and no default value.
return false;
}
}
pExprArguments[index++] = pNewArg;
}
// Here we've found all the arguments, or have default values for them.
CType[] prgTypes = new CType[pCurrentParameters.Count];
for (int i = 0; i < numParameters; i++)
{
if (i < pArguments.prgexpr.Count)
{
pArguments.prgexpr[i] = pExprArguments[i];
}
else
{
pArguments.prgexpr.Add(pExprArguments[i]);
}
prgTypes[i] = pArguments.prgexpr[i].Type;
}
pArguments.carg = pCurrentParameters.Count;
pArguments.types = TypeArray.Allocate(prgTypes);
return true;
}
/////////////////////////////////////////////////////////////////////////////////
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr GenerateOptionalArgument(MethodOrPropertySymbol methprop, CType type, int index)
{
CType pParamType = type;
CType pRawParamType = type.StripNubs();
Expr optionalArgument;
if (methprop.HasDefaultParameterValue(index))
{
CType pConstValType = methprop.GetDefaultParameterValueConstValType(index);
ConstVal cv = methprop.GetDefaultParameterValue(index);
if (pConstValType.IsPredefType(PredefinedType.PT_DATETIME) &&
(pRawParamType.IsPredefType(PredefinedType.PT_DATETIME) || pRawParamType.IsPredefType(PredefinedType.PT_OBJECT) || pRawParamType.IsPredefType(PredefinedType.PT_VALUE)))
{
// This is the specific case where we want to create a DateTime
// but the constval that stores it is a long.
AggregateType dateTimeType = SymbolLoader.GetPredefindType(PredefinedType.PT_DATETIME);
optionalArgument = ExprFactory.CreateConstant(dateTimeType, ConstVal.Get(DateTime.FromBinary(cv.Int64Val)));
}
else if (pConstValType.IsSimpleOrEnumOrString)
{
// In this case, the constval is a simple type (all the numerics, including
// decimal), or an enum or a string. This covers all the substantial values,
// and everything else that can be encoded is just null or default(something).
// For enum parameters, we create a constant of the enum type. For everything
// else, we create the appropriate constant.
optionalArgument = ExprFactory.CreateConstant(
pRawParamType.IsEnumType && pConstValType == pRawParamType.UnderlyingEnumType
? pRawParamType
: pConstValType,
cv);
}
else if ((pParamType.IsReferenceType || pParamType is NullableType) && cv.IsNullRef)
{
// We have an "= null" default value with a reference type or a nullable type.
optionalArgument = ExprFactory.CreateNull();
}
else
{
// We have a default value that is encoded as a nullref, and that nullref is
// interpreted as default(something). For instance, the pParamType could be
// a type parameter type or a non-simple value type.
optionalArgument = ExprFactory.CreateZeroInit(pParamType);
}
}
else
{
// There was no default parameter specified, so generally use default(T),
// except for some cases when the parameter type in metatdata is object.
if (pParamType.IsPredefType(PredefinedType.PT_OBJECT))
{
if (methprop.MarshalAsObject(index))
{
// For [opt] parameters of type object, if we have marshal(iunknown),
// marshal(idispatch), or marshal(interface), then we emit a null.
optionalArgument = ExprFactory.CreateNull();
}
else
{
// Otherwise, we generate Type.Missing
AggregateSymbol agg = SymbolLoader.GetPredefAgg(PredefinedType.PT_MISSING);
Name name = NameManager.GetPredefinedName(PredefinedName.PN_CAP_VALUE);
FieldSymbol field = SymbolLoader.LookupAggMember(name, agg, symbmask_t.MASK_FieldSymbol) as FieldSymbol;
FieldWithType fwt = new FieldWithType(field, agg.getThisType());
ExprField exprField = ExprFactory.CreateField(agg.getThisType(), null, fwt);
optionalArgument = ExprFactory.CreateCast(type, exprField);
}
}
else
{
// Every type aside from object that doesn't have a default value gets
// its default value.
optionalArgument = ExprFactory.CreateZeroInit(pParamType);
}
}
Debug.Assert(optionalArgument != null);
optionalArgument.IsOptionalArgument = true;
return optionalArgument;
}
private static MethodOrPropertySymbol FindMostDerivedMethod(MethodOrPropertySymbol pMethProp, Expr pObject) =>
FindMostDerivedMethod(pMethProp, pObject?.Type);
public static MethodOrPropertySymbol FindMostDerivedMethod(MethodOrPropertySymbol pMethProp, CType pType)
{
bool bIsIndexer = false;
if (!(pMethProp is MethodSymbol method))
{
PropertySymbol prop = (PropertySymbol)pMethProp;
method = prop.GetterMethod ?? prop.SetterMethod;
if (method == null)
{
return null;
}
bIsIndexer = prop is IndexerSymbol;
}
if (!method.isVirtual || pType == null)
{
// if pType is null, this must be a static call.
return method;
}
// Now get the slot method.
var slotMethod = method.swtSlot?.Meth();
if (slotMethod != null)
{
method = slotMethod;
}
if (!(pType is AggregateType agg))
{
// Not something that can have overrides anyway.
return method;
}
for (AggregateSymbol pAggregate = agg.OwningAggregate;
pAggregate?.GetBaseAgg() != null;
pAggregate = pAggregate.GetBaseAgg())
{
for (MethodOrPropertySymbol meth = SymbolLoader.LookupAggMember(method.name, pAggregate, symbmask_t.MASK_MethodSymbol | symbmask_t.MASK_PropertySymbol) as MethodOrPropertySymbol;
meth != null;
meth = meth.LookupNext(symbmask_t.MASK_MethodSymbol | symbmask_t.MASK_PropertySymbol) as MethodOrPropertySymbol)
{
if (!meth.isOverride)
{
continue;
}
if (meth.swtSlot.Sym != null && meth.swtSlot.Sym == method)
{
if (bIsIndexer)
{
Debug.Assert(meth is MethodSymbol);
return ((MethodSymbol)meth).getProperty();
}
else
{
return meth;
}
}
}
}
// If we get here, it means we can have two cases: one is that we have
// a delegate. This is because the delegate invoke method is virtual and is
// an override, but we won't have the slots set up correctly, and will
// not find the base type in the inheritance hierarchy. The second is that
// we're calling off of the base itself.
Debug.Assert(method.parent is AggregateSymbol);
return method;
}
/////////////////////////////////////////////////////////////////////////////////
private bool HasOptionalParameters()
{
MethodOrPropertySymbol methprop = FindMostDerivedMethod(_pCurrentSym, _pGroup.OptionalObject);
return methprop != null && methprop.HasOptionalParameters();
}
/////////////////////////////////////////////////////////////////////////////////
// Returns true if we can either add enough optional parameters to make the
// argument list match, or if we don't need to at all.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private bool AddArgumentsForOptionalParameters()
{
if (_pCurrentParameters.Count <= _pArguments.carg)
{
// If we have enough arguments, or too many, no need to add any optionals here.
return true;
}
// First we need to find the method that we're actually trying to call.
MethodOrPropertySymbol methprop = FindMostDerivedMethod(_pCurrentSym, _pGroup.OptionalObject);
if (methprop == null)
{
return false;
}
// If we're here, we know we're not in a named argument case. As such, we can
// just generate defaults for every missing argument.
int i = _pArguments.carg;
int index = 0;
TypeArray @params = TypeManager.SubstTypeArray(
_pCurrentParameters,
_pCurrentType,
_pGroup.TypeArgs);
Expr[] pArguments = new Expr[_pCurrentParameters.Count - i];
for (; i < @params.Count; i++, index++)
{
if (!methprop.IsParameterOptional(i))
{
// We don't have an optional here, but we need to fill it in.
return false;
}
pArguments[index] = GenerateOptionalArgument(methprop, @params[i], i);
}
// Success. Lets copy them in now.
for (int n = 0; n < index; n++)
{
_pArguments.prgexpr.Add(pArguments[n]);
}
CType[] prgTypes = new CType[@params.Count];
for (int n = 0; n < @params.Count; n++)
{
prgTypes[n] = _pArguments.prgexpr[n].Type;
}
_pArguments.types = TypeArray.Allocate(prgTypes);
_pArguments.carg = @params.Count;
_bArgumentsChangedForNamedOrOptionalArguments = true;
return true;
}
/////////////////////////////////////////////////////////////////////////////////
private static Expr FindArgumentWithName(ArgInfos pArguments, Name pName)
{
List<Expr> prgexpr = pArguments.prgexpr;
for (int i = 0; i < pArguments.carg; i++)
{
Expr expr = prgexpr[i];
if (expr is ExprNamedArgumentSpecification named && named.Name == pName)
{
return expr;
}
}
return null;
}
/////////////////////////////////////////////////////////////////////////////////
private bool NamedArgumentNamesAppearInParameterList(
MethodOrPropertySymbol methprop)
{
// Keep track of the current position in the parameter list so that we can check
// containment from this point onwards as well as complete containment. This is
// for error reporting. The user cannot specify a named argument for a parameter
// that has a fixed argument value.
List<Name> currentPosition = methprop.ParameterNames;
HashSet<Name> names = new HashSet<Name>();
for (int i = 0; i < _pArguments.carg; i++)
{
if (!(_pArguments.prgexpr[i] is ExprNamedArgumentSpecification named))
{
if (!currentPosition.IsEmpty())
{
currentPosition = currentPosition.Tail();
}
continue;
}
Name name = named.Name;
if (!methprop.ParameterNames.Contains(name))
{
_pInvalidSpecifiedName ??= name;
return false;
}
else if (!currentPosition.Contains(name))
{
_pNameUsedInPositionalArgument ??= name;
return false;
}
if (!names.Add(name))
{
_pDuplicateSpecifiedName ??= name;
return false;
}
}
return true;
}
// This method returns true if we have another sym to consider.
// If we've found a match in the current type, and have no more syms to consider in this type, then we
// return false.
private bool GetNextSym(CMemberLookupResults.CMethodIterator iterator)
{
if (!iterator.MoveNext())
{
return false;
}
_pCurrentSym = iterator.CurrentSymbol;
AggregateType type = iterator.CurrentType;
// If our current type is null, this is our first iteration, so set the type.
// If our current type is not null, and we've got a new type now, and we've already matched
// a symbol, then bail out.
if (_pCurrentType != type &&
_pCurrentType != null &&
!_methList.IsEmpty() &&
!_methList.Head().mpwi.GetType().IsInterfaceType)
{
return false;
}
_pCurrentType = type;
// We have a new type. If this type is hidden, we need another type.
while (_HiddenTypes.Contains(_pCurrentType))
{
// Move through this type and get the next one.
for (; iterator.CurrentType == _pCurrentType; iterator.MoveNext()) ;
_pCurrentSym = iterator.CurrentSymbol;
_pCurrentType = iterator.CurrentType;
if (iterator.AtEnd)
{
return false;
}
}
return true;
}
private bool ConstructExpandedParameters()
{
// Deal with params.
if (_pCurrentSym == null || _pArguments == null || _pCurrentParameters == null)
{
return false;
}
if (0 != (_fBindFlags & BindingFlag.BIND_NOPARAMS))
{
return false;
}
if (!_pCurrentSym.isParamArray)
{
return false;
}
// Count the number of optionals in the method. If there are enough optionals
// and actual arguments, then proceed.
{
int numOptionals = 0;
for (int i = _pArguments.carg; i < _pCurrentSym.Params.Count; i++)
{
if (_pCurrentSym.IsParameterOptional(i))
{
numOptionals++;
}
}
if (_pArguments.carg + numOptionals < _pCurrentParameters.Count - 1)
{
return false;
}
}
Debug.Assert(_methList.IsEmpty() || _methList.Head().mpwi.MethProp() != _pCurrentSym);
// Construct the expanded params.
return TryGetExpandedParams(_pCurrentSym.Params, _pArguments.carg, out _pCurrentParameters);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Result DetermineCurrentTypeArgs()
{
TypeArray typeArgs = _pGroup.TypeArgs;
// Get the type args.
if (_pCurrentSym is MethodSymbol methSym && methSym.typeVars.Count != typeArgs.Count)
{
// Can't infer if some type args are specified.
if (typeArgs.Count > 0)
{
if (!_mwtBadArity)
{
_mwtBadArity.Set(methSym, _pCurrentType);
}
return Result.Failure_NoSearchForExpanded;
}
Debug.Assert(methSym.typeVars.Count > 0);
// Try to infer. If we have an errorsym in the type arguments, we know we cant infer,
// but we want to attempt it anyway. We'll mark this as "cant infer" so that we can
// report the appropriate error, but we'll continue inferring, since we want
// error sym to go to any type.
bool inferenceSucceeded = MethodTypeInferrer.Infer(
_pExprBinder, methSym, _pCurrentParameters, _pArguments, out _pCurrentTypeArgs);
if (!inferenceSucceeded)
{
if (_results.IsBetterUninferableResult(_pCurrentTypeArgs))
{
TypeArray pTypeVars = methSym.typeVars;
if (pTypeVars != null && _pCurrentTypeArgs != null && pTypeVars.Count == _pCurrentTypeArgs.Count)
{
_mpwiCantInferInstArg.Set(methSym, _pCurrentType, _pCurrentTypeArgs);
}
else
{
_mpwiCantInferInstArg.Set(methSym, _pCurrentType, pTypeVars);
}
}
return Result.Failure_SearchForExpanded;
}
}
else
{
_pCurrentTypeArgs = typeArgs;
}
return Result.Success;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private bool ArgumentsAreConvertible()
{
bool containsErrorSym = false;
if (_pArguments.carg != 0)
{
UpdateArguments();
for (int ivar = 0; ivar < _pArguments.carg; ivar++)
{
CType var = _pCurrentParameters[ivar];
bool constraintErrors = !TypeBind.CheckConstraints(var, CheckConstraintsFlags.NoErrors);
if (constraintErrors && !DoesTypeArgumentsContainErrorSym(var))
{
_mpwiParamTypeConstraints.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
return false;
}
}
for (int ivar = 0; ivar < _pArguments.carg; ivar++)
{
CType var = _pCurrentParameters[ivar];
containsErrorSym |= DoesTypeArgumentsContainErrorSym(var);
bool fresult;
Expr pArgument = _pArguments.prgexpr[ivar];
// If we have a named argument, strip it to do the conversion.
if (pArgument is ExprNamedArgumentSpecification named)
{
pArgument = named.Value;
}
fresult = _pExprBinder.canConvert(pArgument, var);
// Mark this as a legitimate error if we didn't have any error syms.
if (!fresult && !containsErrorSym)
{
if (ivar > _nArgBest)
{
_nArgBest = ivar;
// If we already have best method for instance methods don't overwrite with extensions
if (!_results.BestResult)
{
_results.BestResult.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
}
}
else if (ivar == _nArgBest && _pArguments.types[ivar] != var)
{
// this is to eliminate the paranoid case of types that are equal but can't convert
// (think ErrorType != ErrorType)
// See if they just differ in out / ref.
CType argStripped = _pArguments.types[ivar] is ParameterModifierType modArg ?
modArg.ParameterType : _pArguments.types[ivar];
CType varStripped = var is ParameterModifierType modVar ? modVar.ParameterType : var;
if (argStripped == varStripped)
{
// If we already have best method for instance methods don't overwrite with extensions
if (!_results.BestResult)
{
_results.BestResult.Set(_pCurrentSym, _pCurrentType, _pCurrentTypeArgs);
}
}
}
return false;
}
}
}
if (containsErrorSym)
{
if (_results.IsBetterUninferableResult(_pCurrentTypeArgs) && _pCurrentSym is MethodSymbol meth)
{
// If we're an instance method then mark us down.
_results.UninferableResult.Set(meth, _pCurrentType, _pCurrentTypeArgs);
}
return false;
}
return true;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void UpdateArguments()
{
// Parameter types might have changed as a result of
// method type inference.
_pCurrentParameters = TypeManager.SubstTypeArray(
_pCurrentParameters, _pCurrentType, _pCurrentTypeArgs);
// It is also possible that an optional argument has changed its value
// as a result of method type inference. For example, when inferring
// from Foo(10) to Foo<T>(T t1, T t2 = default(T)), the fabricated
// argument list starts off as being (10, default(T)). After type
// inference has successfully inferred T as int, it needs to be
// transformed into (10, default(int)) before applicability checking
// notices that default(T) is not assignable to int.
if (_pArguments.prgexpr == null || _pArguments.prgexpr.Count == 0)
{
return;
}
MethodOrPropertySymbol pMethod = null;
for (int iParam = 0; iParam < _pCurrentParameters.Count; ++iParam)
{
Expr pArgument = _pArguments.prgexpr[iParam];
if (!pArgument.IsOptionalArgument)
{
continue;
}
CType pType = _pCurrentParameters[iParam];
if (pType == pArgument.Type)
{
continue;
}
// Argument has changed its type because of method type inference. Recompute it.
if (pMethod == null)
{
pMethod = FindMostDerivedMethod(_pCurrentSym, _pGroup.OptionalObject);
Debug.Assert(pMethod != null);
}
Debug.Assert(pMethod.IsParameterOptional(iParam));
Expr pArgumentNew = GenerateOptionalArgument(pMethod, _pCurrentParameters[iParam], iParam);
_pArguments.prgexpr[iParam] = pArgumentNew;
}
}
private static bool DoesTypeArgumentsContainErrorSym(CType var)
{
if (!(var is AggregateType varAgg))
{
return false;
}
TypeArray typeVars = varAgg.TypeArgsAll;
for (int i = 0; i < typeVars.Count; i++)
{
CType type = typeVars[i];
if (type == null)
{
return true;
}
else if (type is AggregateType)
{
// If we have an agg type sym, check if its type args have errors.
if (DoesTypeArgumentsContainErrorSym(type))
{
return true;
}
}
}
return false;
}
// ----------------------------------------------------------------------------
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void ReportErrorsOnSuccess()
{
// used for Methods and Indexers
Debug.Assert(_pGroup.SymKind == SYMKIND.SK_MethodSymbol || _pGroup.SymKind == SYMKIND.SK_PropertySymbol && 0 != (_pGroup.Flags & EXPRFLAG.EXF_INDEXER));
Debug.Assert(_pGroup.TypeArgs.Count == 0 || _pGroup.SymKind == SYMKIND.SK_MethodSymbol);
Debug.Assert(0 == (_pGroup.Flags & EXPRFLAG.EXF_USERCALLABLE) || _results.BestResult.MethProp().isUserCallable());
if (_pGroup.SymKind == SYMKIND.SK_MethodSymbol)
{
Debug.Assert(_results.BestResult.MethProp() is MethodSymbol);
if (_results.BestResult.TypeArgs.Count > 0)
{
// Check method type variable constraints.
TypeBind.CheckMethConstraints(new MethWithInst(_results.BestResult));
}
}
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private RuntimeBinderException ReportErrorsOnFailure()
{
// First and foremost, report if the user specified a name more than once.
if (_pDuplicateSpecifiedName != null)
{
return ErrorHandling.Error(ErrorCode.ERR_DuplicateNamedArgument, _pDuplicateSpecifiedName);
}
Debug.Assert(_methList.IsEmpty());
// Report inaccessible.
if (_results.InaccessibleResult)
{
// We might have called this, but it is inaccessible...
return CSemanticChecker.ReportAccessError(_results.InaccessibleResult, _pExprBinder.ContextForMemberLookup, GetTypeQualifier(_pGroup));
}
if (_misnamed)
{
// Get exception immediately for the non-trailing named argument being misplaced.
// Handle below for the name not being present at all.
List<Name> paramNames = FindMostDerivedMethod(_misnamed.MethProp(), _pGroup.OptionalObject).ParameterNames;
for (int i = 0; ; ++i)
{
if (i == _pOriginalArguments.carg)
{
// If we're here we had the correct name used for the first params argument.
// Report it as not matching the correct number of arguments below.
break;
}
if (_pOriginalArguments.prgexpr[i] is ExprNamedArgumentSpecification named)
{
Name name = named.Name;
if (paramNames[i] != name)
{
// We have the bad name. Is it misplaced or absent?
if (paramNames.Contains(name))
{
return ErrorHandling.Error(ErrorCode.ERR_BadNonTrailingNamedArgument, name);
}
// Let this be handled by _pInvalidSpecifiedName handling.
_pInvalidSpecifiedName = name;
break;
}
}
}
}
else if (_mpwiBogus)
{
// We might have called this, but it is bogus...
return ErrorHandling.Error(ErrorCode.ERR_BindToBogus, _mpwiBogus);
}
bool bUseDelegateErrors = false;
Name nameErr = _pGroup.Name;
// Check for an invoke.
if (_pGroup.OptionalObject?.Type != null &&
_pGroup.OptionalObject.Type.IsDelegateType &&
_pGroup.Name == NameManager.GetPredefinedName(PredefinedName.PN_INVOKE))
{
Debug.Assert(!_results.BestResult || _results.BestResult.MethProp().getClass().IsDelegate());
Debug.Assert(!_results.BestResult || _results.BestResult.GetType().OwningAggregate.IsDelegate());
bUseDelegateErrors = true;
nameErr = ((AggregateType)_pGroup.OptionalObject.Type).OwningAggregate.name;
}
if (_results.BestResult)
{
// If we had some invalid arguments for best matching.
return ReportErrorsForBestMatching(bUseDelegateErrors);
}
if (_results.UninferableResult || _mpwiCantInferInstArg)
{
if (!_results.UninferableResult)
{
//copy the extension method for which instance argument type inference failed
_results.UninferableResult.Set(_mpwiCantInferInstArg.Sym as MethodSymbol, _mpwiCantInferInstArg.GetType(), _mpwiCantInferInstArg.TypeArgs);
}
Debug.Assert(_results.UninferableResult.Sym is MethodSymbol);
MethWithType mwtCantInfer = new MethWithType(_results.UninferableResult.Meth(), _results.UninferableResult.GetType());
return ErrorHandling.Error(ErrorCode.ERR_CantInferMethTypeArgs, mwtCantInfer);
}
if (_mwtBadArity)
{
int cvar = _mwtBadArity.Meth().typeVars.Count;
return ErrorHandling.Error(cvar > 0 ? ErrorCode.ERR_BadArity : ErrorCode.ERR_HasNoTypeVars, _mwtBadArity, new ErrArgSymKind(_mwtBadArity.Meth()), _pArguments.carg);
}
if (_mpwiParamTypeConstraints)
{
// This will always report an error
TypeBind.CheckMethConstraints(new MethWithInst(_mpwiParamTypeConstraints));
Debug.Fail("Unreachable");
return null;
}
if (_pInvalidSpecifiedName != null)
{
// Give a better message for delegate invoke.
return _pGroup.OptionalObject?.Type is AggregateType agg && agg.OwningAggregate.IsDelegate()
? ErrorHandling.Error(
ErrorCode.ERR_BadNamedArgumentForDelegateInvoke, agg.OwningAggregate.name,
_pInvalidSpecifiedName)
: ErrorHandling.Error(ErrorCode.ERR_BadNamedArgument, _pGroup.Name, _pInvalidSpecifiedName);
}
if (_pNameUsedInPositionalArgument != null)
{
return ErrorHandling.Error(ErrorCode.ERR_NamedArgumentUsedInPositional, _pNameUsedInPositionalArgument);
}
// The number of arguments must be wrong.
if (_fCandidatesUnsupported)
{
return ErrorHandling.Error(ErrorCode.ERR_BindToBogus, nameErr);
}
if (bUseDelegateErrors)
{
Debug.Assert(0 == (_pGroup.Flags & EXPRFLAG.EXF_CTOR));
return ErrorHandling.Error(ErrorCode.ERR_BadDelArgCount, nameErr, _pArguments.carg);
}
if (0 != (_pGroup.Flags & EXPRFLAG.EXF_CTOR))
{
Debug.Assert(!(_pGroup.ParentType is TypeParameterType));
return ErrorHandling.Error(ErrorCode.ERR_BadCtorArgCount, _pGroup.ParentType, _pArguments.carg);
}
return ErrorHandling.Error(ErrorCode.ERR_BadArgCount, nameErr, _pArguments.carg);
}
private RuntimeBinderException ReportErrorsForBestMatching(bool bUseDelegateErrors)
{
if (bUseDelegateErrors)
{
// Point to the Delegate, not the Invoke method
return ErrorHandling.Error(ErrorCode.ERR_BadDelArgTypes, _results.BestResult.GetType());
}
return ErrorHandling.Error(ErrorCode.ERR_BadArgTypes, _results.BestResult);
}
}
}
}
|