|
// 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
{
// Used by bindUserDefinedConversion
internal readonly struct UdConvInfo
{
public readonly MethWithType Meth;
public readonly bool SrcImplicit;
public readonly bool DstImplicit;
public UdConvInfo(MethWithType mwt, bool srcImplicit, bool dstImplicit)
{
Meth = mwt;
SrcImplicit = srcImplicit;
DstImplicit = dstImplicit;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Small wrapper for passing around argument information for the various BindGrpTo methods
// It is used because most things only need the type, but in the case of METHGRPs and ANONMETHs
// the expr is also needed to determine if a conversion is possible
internal sealed class ArgInfos
{
public int carg;
public TypeArray types;
public List<Expr> prgexpr;
}
internal enum ConstCastResult
{
Success, // Constant can be cast to type
Failure, // Constant cannot be cast to type
CheckFailure // Constant cannot be cast to type because of overflow in checked context
// (Note that this only happens when the conversion is explicit; implicit
// conversions never overflow, that's why they're implicit.)
}
internal enum AggCastResult
{
Success, // We found a conversion, stop looking
Failure, // This conversion doesn't work, keep looking
Abort // No possible conversion can work, stop looking
}
internal enum UnaryOperatorSignatureFindResult
{
Match,
Continue,
Return
}
internal enum UnaOpKind
{
Plus,
Minus,
Tilde,
Bang,
IncDec,
Lim
}
[Flags]
internal enum UnaOpMask
{
None = 0,
Plus = 1 << UnaOpKind.Plus,
Minus = 1 << UnaOpKind.Minus,
Tilde = 1 << UnaOpKind.Tilde,
Bang = 1 << UnaOpKind.Bang,
IncDec = 1 << UnaOpKind.IncDec,
// The different combinations needed in operators.cs
Signed = Plus | Minus | Tilde,
Unsigned = Plus | Tilde,
Real = Plus | Minus,
Bool = Bang,
}
[Flags]
internal enum OpSigFlags
{
None = 0,
Convert = 0x01, // Convert the operands before calling the bind method
CanLift = 0x02, // Operator has a lifted form
AutoLift = 0x04, // Standard nullable lifting
// The different combinations needed in operators.cs
Value = Convert | CanLift | AutoLift,
Reference = Convert,
BoolBit = Convert | CanLift,
}
[Flags]
internal enum LiftFlags
{
None = 0,
Lift1 = 0x01,
Lift2 = 0x02,
Convert1 = 0x04,
Convert2 = 0x08,
}
internal enum CheckLvalueKind
{
Assignment,
Increment
}
internal enum BinOpFuncKind
{
BoolBinOp,
BoolBitwiseOp,
DecBinOp,
DelBinOp,
EnumBinOp,
IntBinOp,
RealBinOp,
RefCmpOp,
ShiftOp,
StrBinOp,
StrCmpOp,
None
}
internal enum UnaOpFuncKind
{
BoolUnaOp,
DecUnaOp,
EnumUnaOp,
IntUnaOp,
RealUnaOp,
LiftedIncOpCore,
None
}
internal readonly partial struct ExpressionBinder
{
// ExpressionBinder - General Rules
//
// Express the Contract
//
// Use assertions and naming guidelines to express the contract for methods.
// The most common issue is whether an argument may be null or not. If an
// argument may not be null, then the method must ASSERT that before any other
// code. If an argument may be null then the name of the argument should
// include 'Optional'. The exception to this rule is the input parse tree
// parameter. If the parse tree may be null, then the method name should
// include an 'Opt' suffix. For example bindArgumentList should really be
// named bindArgumentListOpt. Abbreviations should be avoided, but the 'Opt'
// suffix gets an exception because it is used consistently in the language
// spec.
//
//
// Error Tolerant
//
// Do not rely on the input parse tree being complete. Erroneous code may
// result in parse trees with required children missing, or with unexpected
// structure. Find out what the invariants are for the parse tree being
// consumed and code defensively.
//
// Similarly, the result of binding children nodes may not be 'OK'. The child
// node may have contained some semantic errors and the binding code in the
// parent must cope gracefully with the result. For example, an EXPRMEMGRP may
// contain no members.
//
//
// Error Recovery
//
// Always attempt to bind children nodes even if errors have already been
// detected in other children. Always build a new node representing a 'best
// guess' at the semantics of the parse tree. Never discard the results of
// binding a child node even if the binding has errors. Since a new node is
// always produced there should always be a place to add bindings with errors
// to the result.
//
// This ensures that full semantic information for all nodes is produced -
// that the expression binder always produces a 'best guess' for every
// expression in source.
//
//
// Error Reporting
//
// If a child expression has an error, then no new error's for the parent
// should be reported, unless there is no way that the new error was caused
// by any child errors. If child nodes don't have any errors, and the new
// node does have an error, then at least one error must be reported. These
// rules ensure that an error is always reported for erroneous code, and that
// only the most meaningful error is reported from a set of cascading errors.
//
//
// Map Back To the Source
//
// When constructing new expression nodes, attach the appropriate parse tree
// node. The attached parse tree is used for:
// - error location reporting
// - debug sequence points
// - stepping
// - local variable scopes
// - finding the most meaningful expression for a parse tree node
//
//
// Meaning not Implementation
//
// The expression trees resulting from the initial binding pass should
// represent the semantics of the input source code. There should be a
// direct mapping between the newly constructed expression and the input
// parse tree.
//
// The Whidbey codebase had the habit of producing expressions in the initial
// binding which were closer in representation to the generated IL than the
// input source code. In Orcas all transformations which may lose semantic
// information about the source must be done after the initial expression
// binding phase.
//
// Special Cases
//
// - Constant folding - Constant folding is a semantic losing
// transformation which must be performed to complete expression
// analysis. When creating a folded constant, create an expression
// node representing the unfolded expression, then pass this as a
// child expression of a new constant expression.
// - Color Color - The new Type or Instance expression covers this case.
// It is produced from bindSimpleName.
// - Method Group - Method groups should be preserved in expression trees.
// This includes as children of Call expressions, and delegate construction
// nodes.
// - Type Binding - This is a big one. Whenever a type is bound in an
// expression, the binding of the component parts of the type must be
// preserved. This includes every identifier in a dotted type or
// namespace name, as well as the type binding information for the
// type arguments of constructed types. The semantic information for
// intermediate type binding results is represented by an
// EXPRTYPEORNAMESPACE. Types can be bound in several places in
// expressions:
// - Sizeof
// - Typeof
// - New
// - Is/As
// - Cast
// - Type Arguments supplied to generic method calls.
// - Left hand side of a dot operator.
// - Parameter types in anonymous methods and lambdas.
// - Local Variables
//
//
// Want to eventually Have's
//
// Only build the new node once all children have been built.
// Factory should require all children as arguments.
// Factory method sets the "Do Children have Errors?" bit - not done manually.
// Once constructed Expression trees are not mutated - doesn't work easily for statements unfortunately.
private delegate Expr PfnBindBinOp(ExpressionBinder binder, ExpressionKind ek, EXPRFLAG flags, Expr op1, Expr op2);
private delegate Expr PfnBindUnaOp(ExpressionBinder binder, ExpressionKind ek, EXPRFLAG flags, Expr op);
public BindingContext Context { get; }
public ExpressionBinder(BindingContext context)
{
Context = context;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static AggregateType GetPredefindType(PredefinedType pt)
{
Debug.Assert(pt != PredefinedType.PT_VOID); // use getVoidType()
return SymbolLoader.GetPredefindType(pt);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateAssignmentConversion(Expr op1, Expr op2, bool allowExplicit) =>
allowExplicit ? mustCastCore(op2, op1.Type, 0) : mustConvertCore(op2, op1.Type);
////////////////////////////////////////////////////////////////////////////////
// Bind the simple assignment operator =.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
public Expr BindAssignment(Expr op1, Expr op2, bool allowExplicit)
{
Debug.Assert(op1 is ExprCast
|| op1 is ExprArrayIndex
|| op1 is ExprCall
|| op1 is ExprProperty
|| op1 is ExprClass
|| op1 is ExprField);
CheckLvalue(op1, CheckLvalueKind.Assignment);
op2 = GenerateAssignmentConversion(op1, op2, allowExplicit);
return GenerateOptimizedAssignment(op1, op2);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal Expr BindArrayIndexCore(Expr pOp1, Expr pOp2)
{
CType pIntType = GetPredefindType(PredefinedType.PT_INT);
ArrayType pArrayType = pOp1.Type as ArrayType;
Debug.Assert(pArrayType != null);
CType elementType = pArrayType.ElementType;
CheckUnsafe(elementType); // added to the binder so we don't bind to pointer ops
// Check the rank of the array against the number of indices provided, and
// convert the indexes to ints
CType pDestType = ChooseArrayIndexType(pOp2);
ExpressionBinder binder = this;
Expr transformedIndices = pOp2.Map(
x =>
{
Expr pTemp = binder.MustConvertWithSuppressedMessage(x, pDestType);
return pDestType == pIntType
? pTemp
: ExprFactoryCreateCastWithSuppressedMessage(EXPRFLAG.EXF_INDEXEXPR, pDestType, pTemp);
});
// Allocate a new expression, the type is the element type of the array.
// Array index operations are always lvalues.
return ExprFactory.CreateArrayIndex(elementType, pOp1, transformedIndices);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Workarounds https://github.com/mono/linker/issues/1416. All usages are marked as unsafe.")]
private Expr MustConvertWithSuppressedMessage(Expr x, CType pDestType)
=> mustConvert(x, pDestType);
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Workarounds https://github.com/mono/linker/issues/1416. All usages are marked as unsafe.")]
private static ExprCast ExprFactoryCreateCastWithSuppressedMessage(EXPRFLAG flags, CType type, Expr argument)
=> ExprFactory.CreateCast(flags, type, argument);
////////////////////////////////////////////////////////////////////////////////
// Create a cast node with the given expression flags.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void bindSimpleCast(Expr exprSrc, CType typeDest, out Expr pexprDest) =>
bindSimpleCast(exprSrc, typeDest, out pexprDest, 0);
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void bindSimpleCast(Expr exprSrc, CType typeDest, out Expr pexprDest, EXPRFLAG exprFlags)
{
Debug.Assert(typeDest != null);
// If the source is a constant, and cast is really simple (no change in fundamental
// type, no flags), then create a new constant node with the new type instead of
// creating a cast node. This allows compile-time constants to be easily recognized.
Expr exprConst = exprSrc.GetConst();
// Make the cast expr anyway, and if we find that we have a constant, then set the cast expr
// as the original tree for the constant. Otherwise, return the cast expr.
ExprCast exprCast = ExprFactory.CreateCast(exprFlags, typeDest, exprSrc);
if (Context.Checked)
{
exprCast.Flags |= EXPRFLAG.EXF_CHECKOVERFLOW;
}
// Check if we have a compile time constant. If we do, create a constant for it and set the
// original tree to the cast.
if (exprConst is ExprConstant constant && exprFlags == 0 &&
exprSrc.Type.FundamentalType == typeDest.FundamentalType &&
(!exprSrc.Type.IsPredefType(PredefinedType.PT_STRING) || constant.Val.IsNullRef))
{
ExprConstant expr = ExprFactory.CreateConstant(typeDest, constant.Val);
pexprDest = expr;
return;
}
pexprDest = exprCast;
Debug.Assert(exprCast.Argument != null);
}
////////////////////////////////////////////////////////////////////////////////
// Binds a call to a method, return type is an error or an EXPRCALL.
//
// tree - ParseTree for error messages
// pObject - pObject to call method on
// pmwi - Meth to bind to. This will be morphed when we remap to an override.
// args - arguments
// exprFlags - Flags to put on the generated expr
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private ExprCall BindToMethod(MethWithInst mwi, Expr pArguments, ExprMemberGroup pMemGroup, MemLookFlags flags)
{
Debug.Assert(mwi.Sym is MethodSymbol && (!mwi.Meth().isOverride || mwi.Meth().isHideByName));
Debug.Assert(pMemGroup != null);
Expr pObject = pMemGroup.OptionalObject;
CType callingObjectType = pObject?.Type;
PostBindMethod(mwi);
pObject = AdjustMemberObject(mwi, pObject);
pMemGroup.OptionalObject = pObject;
CType pReturnType;
if ((flags & (MemLookFlags.Ctor | MemLookFlags.NewObj)) == (MemLookFlags.Ctor | MemLookFlags.NewObj))
{
pReturnType = mwi.Ats;
}
else
{
pReturnType = TypeManager.SubstType(mwi.Meth().RetType, mwi.GetType(), mwi.TypeArgs);
}
ExprCall pResult = ExprFactory.CreateCall(0, pReturnType, pArguments, pMemGroup, mwi);
// Set the return type and flags for constructors.
if ((flags & MemLookFlags.Ctor) != 0)
{
if ((flags & MemLookFlags.NewObj) != 0)
{
pResult.Flags |= EXPRFLAG.EXF_NEWOBJCALL | EXPRFLAG.EXF_CANTBENULL;
}
else
{
Debug.Assert(pResult.Type == VoidType.Instance);
}
}
verifyMethodArgs(pResult, callingObjectType);
return pResult;
}
////////////////////////////////////////////////////////////////////////////////
// Construct the Expr node which corresponds to a field expression
// for a given field and pObject pointer.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal Expr BindToField(Expr pOptionalObject, FieldWithType fwt, BindingFlag bindFlags)
{
Debug.Assert(fwt.GetType() != null && fwt.Field().getClass() == fwt.GetType().OwningAggregate);
CType pFieldType = TypeManager.SubstType(fwt.Field().GetType(), fwt.GetType());
pOptionalObject = AdjustMemberObject(fwt, pOptionalObject);
CheckUnsafe(pFieldType); // added to the binder so we don't bind to pointer ops
// lvalue if the object is an lvalue (or it's static) and the field is not readonly.
// Since dynamic objects for fields come from locals or casts/conversions on locals
// (never properties) and hence always have EXF_LVALUE set, the first part of this is
// always true, leaving the rest to be determined by the field ctor
AssertObjectIsLvalue(pOptionalObject);
ExprField pResult = ExprFactory.CreateField(pFieldType, pOptionalObject, fwt);
Debug.Assert(BindingFlag.BIND_MEMBERSET == (BindingFlag)EXPRFLAG.EXF_MEMBERSET);
pResult.Flags |= (EXPRFLAG)(bindFlags & BindingFlag.BIND_MEMBERSET);
return pResult;
}
////////////////////////////////////////////////////////////////////////////////
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal ExprProperty BindToProperty(Expr pObject, PropWithType pwt, BindingFlag bindFlags, Expr args, ExprMemberGroup pMemGroup)
{
Debug.Assert(pwt.Sym is PropertySymbol &&
pwt.GetType() != null &&
pwt.Prop().getClass() == pwt.GetType().OwningAggregate);
Debug.Assert(pwt.Prop().Params.Count == 0 || pwt.Prop() is IndexerSymbol);
// We keep track of the type of the pObject which we're doing the call through so that we can report
// protection access errors later, either below when binding the get, or later when checking that
// the setter is actually an lvalue.
Expr pObjectThrough = pObject;
PostBindProperty(pwt, out MethWithType mwtGet, out MethWithType mwtSet);
if (mwtGet &&
(!mwtSet ||
mwtSet.GetType() == mwtGet.GetType() ||
SymbolLoader.HasBaseConversion(mwtGet.GetType(), mwtSet.GetType())
)
)
{
pObject = AdjustMemberObject(mwtGet, pObject);
}
else if (mwtSet)
{
pObject = AdjustMemberObject(mwtSet, pObject);
}
else
{
pObject = AdjustMemberObject(pwt, pObject);
}
pMemGroup.OptionalObject = pObject;
CType pReturnType = TypeManager.SubstType(pwt.Prop().RetType, pwt.GetType());
// if we are doing a get on this thing, and there is no get, and
// most importantly, we are not leaving the arguments to be bound by the array index
// then error...
if ((bindFlags & BindingFlag.BIND_RVALUEREQUIRED) != 0)
{
if (!mwtGet)
{
throw ErrorHandling.Error(ErrorCode.ERR_PropertyLacksGet, pwt);
}
CType type = null;
if (pObjectThrough != null)
{
type = pObjectThrough.Type;
}
ACCESSERROR error = CSemanticChecker.CheckAccess2(mwtGet.Meth(), mwtGet.GetType(), ContextForMemberLookup, type);
if (error != ACCESSERROR.ACCESSERROR_NOERROR)
{
// if the get exists, but is not accessible, give an error.
if (error == ACCESSERROR.ACCESSERROR_NOACCESSTHRU)
{
throw ErrorHandling.Error(ErrorCode.ERR_BadProtectedAccess, pwt, type, ContextForMemberLookup);
}
throw ErrorHandling.Error(ErrorCode.ERR_InaccessibleGetter, pwt);
}
}
ExprProperty result = ExprFactory.CreateProperty(pReturnType, pObjectThrough, args, pMemGroup, pwt, mwtSet);
if (result.OptionalArguments != null)
{
verifyMethodArgs(result, pObjectThrough?.Type);
}
AssertObjectIsLvalue(result.MemberGroup.OptionalObject);
return result;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal Expr bindUDUnop(ExpressionKind ek, Expr arg)
{
Name pName = ExpressionKindName(ek);
Debug.Assert(pName != null);
CType typeSrc = arg.Type;
LAgain:
switch (typeSrc.TypeKind)
{
case TypeKind.TK_NullableType:
typeSrc = typeSrc.StripNubs();
goto LAgain;
case TypeKind.TK_AggregateType:
if (!typeSrc.IsClassType && !typeSrc.IsStructType || ((AggregateType)typeSrc).OwningAggregate.IsSkipUDOps())
return null;
break;
default:
return null;
}
ArgInfos info = new ArgInfos();
info.carg = 1;
FillInArgInfoFromArgList(info, arg);
List<CandidateFunctionMember> methFirstList = new List<CandidateFunctionMember>();
MethodSymbol methCur = null;
AggregateType atsCur = (AggregateType)typeSrc;
while (true)
{
// Find the next operator.
methCur = (methCur == null
? SymbolLoader.LookupAggMember(pName, atsCur.OwningAggregate, symbmask_t.MASK_MethodSymbol)
: methCur.LookupNext(symbmask_t.MASK_MethodSymbol)) as MethodSymbol;
if (methCur == null)
{
// Find the next type.
// If we've found some applicable methods in a class then we don't need to look any further.
if (!methFirstList.IsEmpty())
{
break;
}
atsCur = atsCur.BaseClass;
if (atsCur == null)
{
break;
}
continue;
}
// Only look at operators with 1 args.
if (!methCur.isOperator || methCur.Params.Count != 1)
{
continue;
}
Debug.Assert(methCur.typeVars.Count == 0);
TypeArray paramsCur = TypeManager.SubstTypeArray(methCur.Params, atsCur);
CType typeParam = paramsCur[0];
NullableType nubParam;
if (canConvert(arg, typeParam))
{
methFirstList.Add(new CandidateFunctionMember(
new MethPropWithInst(methCur, atsCur, TypeArray.Empty),
paramsCur,
0,
false));
}
else if (typeParam.IsNonNullableValueType &&
TypeManager.SubstType(methCur.RetType, atsCur).IsNonNullableValueType &&
canConvert(arg, nubParam = TypeManager.GetNullable(typeParam)))
{
methFirstList.Add(new CandidateFunctionMember(
new MethPropWithInst(methCur, atsCur, TypeArray.Empty),
TypeArray.Allocate(nubParam),
1,
false));
}
}
if (methFirstList.IsEmpty())
return null;
CandidateFunctionMember pmethAmbig1;
CandidateFunctionMember pmethAmbig2;
CandidateFunctionMember pmethBest = FindBestMethod(methFirstList, null, info, out pmethAmbig1, out pmethAmbig2);
if (pmethBest == null)
{
// No winner, so its an ambiguous call...
throw ErrorHandling.Error(ErrorCode.ERR_AmbigCall, pmethAmbig1.mpwi, pmethAmbig2.mpwi);
}
ExprCall call;
if (pmethBest.ctypeLift != 0)
{
call = BindLiftedUDUnop(arg, pmethBest.@params[0], pmethBest.mpwi);
}
else
{
call = BindUDUnopCall(arg, pmethBest.@params[0], pmethBest.mpwi);
}
return ExprFactory.CreateUserDefinedUnaryOperator(ek, call.Type, arg, call, pmethBest.mpwi);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private ExprCall BindLiftedUDUnop(Expr arg, CType typeArg, MethPropWithInst mpwi)
{
CType typeRaw = typeArg.StripNubs();
if (!(arg.Type is NullableType) || !canConvert(arg.Type.StripNubs(), typeRaw, CONVERTTYPE.NOUDC))
{
// Convert then lift.
arg = mustConvert(arg, typeArg);
}
Debug.Assert(arg.Type is NullableType);
CType typeRet = TypeManager.SubstType(mpwi.Meth().RetType, mpwi.GetType());
if (!(typeRet is NullableType))
{
typeRet = TypeManager.GetNullable(typeRet);
}
// First bind the non-lifted version for errors.
Expr nonLiftedArg = mustCast(arg, typeRaw);
ExprCall nonLiftedResult = BindUDUnopCall(nonLiftedArg, typeRaw, mpwi);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mpwi);
ExprCall call = ExprFactory.CreateCall(0, typeRet, arg, pMemGroup, null);
call.MethWithInst = new MethWithInst(mpwi);
call.CastOfNonLiftedResultToLiftedType = mustCast(nonLiftedResult, typeRet, 0);
call.NullableCallLiftKind = NullableCallLiftKind.Operator;
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private ExprCall BindUDUnopCall(Expr arg, CType typeArg, MethPropWithInst mpwi)
{
CType typeRet = TypeManager.SubstType(mpwi.Meth().RetType, mpwi.GetType());
CheckUnsafe(typeRet); // added to the binder so we don't bind to pointer ops
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mpwi);
ExprCall call = ExprFactory.CreateCall(0, typeRet, mustConvert(arg, typeArg), pMemGroup, null);
call.MethWithInst = new MethWithInst(mpwi);
verifyMethodArgs(call, mpwi.GetType());
return call;
}
////////////////////////////////////////////////////////////////////////////////
// Given a method group or indexer group, bind it to the arguments for an
// invocation.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private GroupToArgsBinderResult BindMethodGroupToArgumentsCore(BindingFlag bindFlags, ExprMemberGroup grp, Expr args, int carg, NamedArgumentsKind namedArgumentsKind)
{
ArgInfos pargInfo = new ArgInfos { carg = carg };
FillInArgInfoFromArgList(pargInfo, args);
ArgInfos pOriginalArgInfo = new ArgInfos { carg = carg };
FillInArgInfoFromArgList(pOriginalArgInfo, args);
GroupToArgsBinder binder = new GroupToArgsBinder(this, bindFlags, grp, pargInfo, pOriginalArgInfo, namedArgumentsKind);
binder.Bind();
return binder.GetResultsOfBind();
}
////////////////////////////////////////////////////////////////////////////////
// Given a method group or indexer group, bind it to the arguments for an
// invocation.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal ExprWithArgs BindMethodGroupToArguments(BindingFlag bindFlags, ExprMemberGroup grp, Expr args)
{
Debug.Assert(grp.SymKind == SYMKIND.SK_MethodSymbol || grp.SymKind == SYMKIND.SK_PropertySymbol && ((grp.Flags & EXPRFLAG.EXF_INDEXER) != 0));
// Count the args.
int carg = CountArguments(args);
Debug.Assert(grp.Name != null);
// Do we have named arguments specified, are they after fixed arguments (can position) or
// non-trailing (can rule out methods only).
NamedArgumentsKind namedKind = FindNamedArgumentsType(args);
MethPropWithInst mpwiBest = BindMethodGroupToArgumentsCore(bindFlags, grp, args, carg, namedKind).BestResult;
if (grp.SymKind == SYMKIND.SK_PropertySymbol)
{
Debug.Assert((grp.Flags & EXPRFLAG.EXF_INDEXER) != 0);
return BindToProperty(grp.OptionalObject, new PropWithType(mpwiBest), bindFlags, args, grp);
}
return BindToMethod(new MethWithInst(mpwiBest), args, grp, (MemLookFlags)grp.Flags);
}
/////////////////////////////////////////////////////////////////////////////////
public enum NamedArgumentsKind
{
None,
Positioning,
NonTrailing
}
private static NamedArgumentsKind FindNamedArgumentsType(Expr args)
{
Expr list = args;
while (list != null)
{
Expr arg;
if (list is ExprList next)
{
arg = next.OptionalElement;
list = next.OptionalNextListNode;
}
else
{
arg = list;
list = null;
}
Debug.Assert(arg != null);
if (arg is ExprNamedArgumentSpecification)
{
while (list != null)
{
if (list is ExprList nextList)
{
arg = nextList.OptionalElement;
list = nextList.OptionalNextListNode;
}
else
{
arg = list;
list = null;
}
if (!(arg is ExprNamedArgumentSpecification))
{
return NamedArgumentsKind.NonTrailing;
}
}
return NamedArgumentsKind.Positioning;
}
}
return NamedArgumentsKind.None;
}
////////////////////////////////////////////////////////////////////////////////
// Report a bad operator types error to the user.
private static RuntimeBinderException BadOperatorTypesError(Expr pOperand1, Expr pOperand2)
{
Debug.Assert(pOperand1 != null);
Debug.Assert(pOperand1.Type != null);
// This is a hack, but we need to store the operation somewhere... the first argument's as
// good a place as any.
string strOp = pOperand1.ErrorString;
if (pOperand2 != null)
{
Debug.Assert(pOperand2.Type != null);
return ErrorHandling.Error(ErrorCode.ERR_BadBinaryOps, strOp, pOperand1.Type, pOperand2.Type);
}
return ErrorHandling.Error(ErrorCode.ERR_BadUnaryOp, strOp, pOperand1.Type);
}
private static ErrorCode GetStandardLvalueError(CheckLvalueKind kind)
{
Debug.Assert(kind >= CheckLvalueKind.Assignment && kind <= CheckLvalueKind.Increment);
return kind == CheckLvalueKind.Increment
? ErrorCode.ERR_IncrementLvalueExpected
: ErrorCode.ERR_AssgLvalueExpected;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void CheckLvalueProp(ExprProperty prop)
{
Debug.Assert(prop != null);
Debug.Assert(prop.isLvalue());
// We have an lvalue property. Give an error if this is an inaccessible property.
CType type = null;
if (prop.OptionalObjectThrough != null)
{
type = prop.OptionalObjectThrough.Type;
}
CheckPropertyAccess(prop.MethWithTypeSet, prop.PropWithTypeSlot, type);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void CheckPropertyAccess(MethWithType mwt, PropWithType pwtSlot, CType type)
{
switch (CSemanticChecker.CheckAccess2(mwt.Meth(), mwt.GetType(), ContextForMemberLookup, type))
{
case ACCESSERROR.ACCESSERROR_NOACCESSTHRU:
throw ErrorHandling.Error(ErrorCode.ERR_BadProtectedAccess, pwtSlot, type, ContextForMemberLookup);
case ACCESSERROR.ACCESSERROR_NOACCESS:
throw ErrorHandling.Error(mwt.Meth().isSetAccessor() ? ErrorCode.ERR_InaccessibleSetter : ErrorCode.ERR_InaccessibleGetter, pwtSlot);
}
}
////////////////////////////////////////////////////////////////////////////////
// A false return means not to process the expr any further - it's totally out
// of place. For example - a method group or an anonymous method.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void CheckLvalue(Expr expr, CheckLvalueKind kind)
{
if (expr.isLvalue())
{
if (expr is ExprProperty prop)
{
CheckLvalueProp(prop);
}
return;
}
Debug.Assert(!(expr is ExprLocal));
Debug.Assert(!(expr is ExprMemberGroup));
switch (expr.Kind)
{
case ExpressionKind.Property:
ExprProperty prop = (ExprProperty)expr;
Debug.Assert(!prop.MethWithTypeSet || ExprProperty.HasIsExternalInitModifier(prop.MethWithTypeSet));
// Assigning to a property without a setter.
// If we have
// bool? b = true; (bool)b = false;
// then this is realized immediately as
// b.Value = false;
// and no ExpressionKind.EK_CAST is generated. We'd rather not give a "you're writing
// to a read-only property" error in the case where the property access
// is not explicit in the source code. Fortunately in this case the
// cast is still present in the parse tree, so we can look for it.
// POSSIBLE ERROR: It would be nice to also give this error for other situations
// POSSIBLE ERROR: in which the user is attempting to assign to a value, such as
// POSSIBLE ERROR: an explicit (bool)b.Value = false;
// POSSIBLE ERROR: Unfortunately we cannot use this trick in that situation because
// POSSIBLE ERROR: we've already discarded the OperatorKind.OP_CAST node. (This is an SyntaxKind.Dot).
// SPEC VIOLATION: More generally:
// SPEC VIOLATION: The spec states that the result of any cast is a value, not a
// SPEC VIOLATION: variable. Unfortunately we do not correctly implement this
// SPEC VIOLATION: and we probably should not start implementing it because this
// SPEC VIOLATION: would be a breaking change. We currently discard "no op" casts
// SPEC VIOLATION: very aggressively rather than generating an ExpressionKind.EK_CAST node.
throw ErrorHandling.Error(ErrorCode.ERR_AssgReadonlyProp, prop.PropWithTypeSlot);
case ExpressionKind.Field:
ExprField field = (ExprField)expr;
Debug.Assert(field.FieldWithType.Field().isReadOnly);
throw ErrorHandling.Error(
field.FieldWithType.Field().isStatic
? ErrorCode.ERR_AssgReadonlyStatic
: ErrorCode.ERR_AssgReadonly);
default:
throw ErrorHandling.Error(GetStandardLvalueError(kind));
}
}
private static void PostBindMethod(MethWithInst pMWI)
{
MethodSymbol meth = pMWI.Meth();
if (meth.RetType != null)
{
CheckUnsafe(meth.RetType);
// We need to check unsafe on the parameters as well, since we cannot check in conversion.
foreach (CType type in meth.Params.Items)
{
CheckUnsafe(type);
}
}
}
private static void PostBindProperty(PropWithType pwt, out MethWithType pmwtGet, out MethWithType pmwtSet)
{
PropertySymbol prop = pwt.Prop();
Debug.Assert(prop != null);
// Get the accessors.
pmwtGet = prop.GetterMethod != null
? new MethWithType(prop.GetterMethod, pwt.GetType())
: new MethWithType();
pmwtSet = prop.SetterMethod != null
? new MethWithType(prop.SetterMethod, pwt.GetType())
: new MethWithType();
if (prop.RetType != null)
{
CheckUnsafe(prop.RetType);
}
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr AdjustMemberObject(SymWithType swt, Expr pObject)
{
// Assert that the type is present and is an instantiation of the member's parent.
Debug.Assert(swt.GetType() != null && swt.GetType().OwningAggregate == swt.Sym.parent as AggregateSymbol);
bool bIsMatchingStatic = IsMatchingStatic(swt, pObject);
bool isStatic = swt.Sym.isStatic;
// If our static doesn't match, bail out of here.
if (!bIsMatchingStatic)
{
if (isStatic)
{
// If we have a mismatched static, a static method, and the binding flag
// that tells us we're binding simple names, then insert a type here instead.
if ((pObject.Flags & EXPRFLAG.EXF_SIMPLENAME) != 0)
{
// We've made the static match now.
return null;
}
throw ErrorHandling.Error(ErrorCode.ERR_ObjectProhibited, swt);
}
throw ErrorHandling.Error(ErrorCode.ERR_ObjectRequired, swt);
}
// At this point, all errors for static invocations have been reported, and
// the object has been nulled out. So return out of here.
if (isStatic)
{
return null;
}
// If we're in a constructor, then bail.
if ((swt.Sym is MethodSymbol) && swt.Meth().IsConstructor())
{
return pObject;
}
if (pObject == null)
{
return null;
}
CType typeObj = pObject.Type;
CType typeTmp;
if (typeObj is NullableType nubTypeObj && (typeTmp = nubTypeObj.GetAts()) != swt.GetType())
{
typeObj = typeTmp;
}
if (typeObj is TypeParameterType || typeObj is AggregateType)
{
AggregateSymbol aggCalled = swt.Sym.parent as AggregateSymbol;
Debug.Assert(swt.GetType().OwningAggregate == aggCalled);
pObject = tryConvert(pObject, swt.GetType(), CONVERTTYPE.NOUDC);
Debug.Assert(pObject != null);
}
return pObject;
}
/////////////////////////////////////////////////////////////////////////////////
private static bool IsMatchingStatic(SymWithType swt, Expr pObject)
{
Symbol pSym = swt.Sym;
// Instance constructors are always ok, static constructors are never ok.
if (pSym is MethodSymbol meth && meth.IsConstructor())
{
return !meth.isStatic;
}
bool isStatic = swt.Sym.isStatic;
if (isStatic)
{
// If we're static and we don't have an object then we're ok.
if (pObject == null)
{
return true;
}
if ((pObject.Flags & EXPRFLAG.EXF_SAMENAMETYPE) == 0)
{
return false;
}
}
else if (pObject == null)
{
// We're not static, and we don't have an object. This is ok in certain scenarios:
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// this determines whether the expression as an pObject of a prop or field is an
// lvalue
[Conditional("DEBUG")]
private static void AssertObjectIsLvalue(Expr pObject)
{
Debug.Assert(
pObject == null || // statics are always lvalues
(((pObject.Flags & EXPRFLAG.EXF_LVALUE) != 0) && (pObject.Kind != ExpressionKind.Property)) ||
// things marked as lvalues have props/fields which are lvalues, with one exception: props of structs
// do not have fields/structs as lvalues
!pObject.Type.IsStructOrEnum
// non-struct types are lvalues (such as non-struct method returns)
);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void verifyMethodArgs(ExprWithArgs call, CType callingObjectType)
{
Debug.Assert(call != null);
Expr argsPtr = call.OptionalArguments;
SymWithType swt = call.GetSymWithType();
MethodOrPropertySymbol mp = swt.Sym as MethodOrPropertySymbol;
TypeArray pTypeArgs = (call as ExprCall)?.MethWithInst.TypeArgs;
Expr newArgs;
AdjustCallArgumentsForParams(callingObjectType, swt.GetType(), mp, pTypeArgs, argsPtr, out newArgs);
call.OptionalArguments = newArgs;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private void AdjustCallArgumentsForParams(CType callingObjectType, CType type, MethodOrPropertySymbol mp, TypeArray pTypeArgs, Expr argsPtr, out Expr newArgs)
{
Debug.Assert(mp != null);
Debug.Assert(mp.Params != null);
newArgs = null;
Expr newArgsTail = null;
MethodOrPropertySymbol mostDerivedMethod = GroupToArgsBinder.FindMostDerivedMethod(mp, callingObjectType);
int paramCount = mp.Params.Count;
TypeArray @params = mp.Params;
int iDst = 0;
int argCount = ExpressionIterator.Count(argsPtr);
Debug.Assert(!Array.Exists(@params.Items, p => p is ArgumentListType)); // We should never have picked a varargs method to bind to.
bool bDontFixParamArray = false;
ExpressionIterator it = new ExpressionIterator(argsPtr);
if (argsPtr == null)
{
if (mp.isParamArray)
goto FIXUPPARAMLIST;
return;
}
for (; !it.AtEnd(); it.MoveNext())
{
Expr indir = it.Current();
// this will splice the optional arguments into the list
if (indir.Type is ParameterModifierType)
{
if (paramCount != 0)
paramCount--;
ExprFactory.AppendItemToList(indir, ref newArgs, ref newArgsTail);
}
else if (paramCount != 0)
{
if (paramCount == 1 && mp.isParamArray && argCount > mp.Params.Count)
{
// we arrived at the last formal, and we have more than one actual, so
// we need to put the rest in an array...
goto FIXUPPARAMLIST;
}
Expr argument = indir;
Expr rval;
if (argument is ExprNamedArgumentSpecification named)
{
int index = 0;
// If we're named, look for the type of the matching name.
foreach (Name i in mostDerivedMethod.ParameterNames)
{
if (i == named.Name)
{
break;
}
index++;
}
Debug.Assert(index != mp.Params.Count);
CType substDestType = TypeManager.SubstType(@params[index], type, pTypeArgs);
// If we cant convert the argument and we're the param array argument, then deal with it.
if (!canConvert(named.Value, substDestType) &&
mp.isParamArray && index == mp.Params.Count - 1)
{
// We have a param array, but we're not at the end yet. This will happen
// with named arguments when the user specifies a name for the param array,
// and its not an actual array.
//
// For example:
// void Foo(int y, params int[] x);
// ...
// Foo(x:1, y:1);
CType arrayType = (ArrayType)TypeManager.SubstType(mp.Params[mp.Params.Count - 1], type, pTypeArgs);
// Use an EK_ARRINIT even in the empty case so empty param arrays in attributes work.
ExprArrayInit arrayInit = ExprFactory.CreateArrayInit(arrayType, null, null, s_zero);
arrayInit.GeneratedForParamArray = true;
arrayInit.OptionalArguments = named.Value;
named.Value = arrayInit;
bDontFixParamArray = true;
}
else
{
// Otherwise, force the conversion and get errors if needed.
named.Value = tryConvert(named.Value, substDestType);
}
rval = argument;
}
else
{
CType substDestType = TypeManager.SubstType(@params[iDst], type, pTypeArgs);
rval = tryConvert(indir, substDestType);
}
if (rval == null)
{
// the last arg failed to fix up, so it must fixup into the array element
// if we have a param array (we will be passing a 1 element array...)
if (mp.isParamArray && paramCount == 1 && argCount >= mp.Params.Count)
{
goto FIXUPPARAMLIST;
}
else
{
// This is either the error case that the args are of the wrong type,
// or that we have some optional arguments being used. Either way,
// we won't need to expand the param array.
return;
}
}
Debug.Assert(rval != null);
indir = rval;
ExprFactory.AppendItemToList(rval, ref newArgs, ref newArgsTail);
paramCount--;
}
// note that destype might not be valid if we are in varargs, but then we won't ever use it...
iDst++;
if (paramCount != 0 && mp.isParamArray && iDst == argCount)
{
// we run out of actuals, but we still have formals, so this is an empty array being passed
// into the last param...
indir = null;
it.MoveNext();
goto FIXUPPARAMLIST;
}
}
return;
FIXUPPARAMLIST:
if (bDontFixParamArray)
{
// We've already fixed the param array for named arguments.
return;
}
// we need to create an array and put it as the last arg...
CType substitutedArrayType = TypeManager.SubstType(mp.Params[mp.Params.Count - 1], type, pTypeArgs);
if (!(substitutedArrayType is ArrayType subArr) || !subArr.IsSZArray)
{
// Invalid type for params array parameter. Happens in LAF scenarios, e.g.
//
// void Foo(int i, params int ar = null) { }
// ...
// Foo(1);
return;
}
CType elementType = subArr.ElementType;
// Use an EK_ARRINIT even in the empty case so empty param arrays in attributes work.
ExprArrayInit exprArrayInit = ExprFactory.CreateArrayInit(substitutedArrayType, null, null, s_zero);
exprArrayInit.GeneratedForParamArray = true;
if (it.AtEnd())
{
exprArrayInit.DimensionSizes[0] = 0;
exprArrayInit.OptionalArguments = null;
if (argsPtr == null)
{
argsPtr = exprArrayInit;
}
else
{
argsPtr = ExprFactory.CreateList(argsPtr, exprArrayInit);
}
ExprFactory.AppendItemToList(exprArrayInit, ref newArgs, ref newArgsTail);
}
else
{
// Go through the list - for each argument, do the conversion and append it to the new list.
Expr newList = null;
Expr newListTail = null;
int count = 0;
for (; !it.AtEnd(); it.MoveNext())
{
Expr expr = it.Current();
count++;
if (expr is ExprNamedArgumentSpecification named)
{
named.Value = tryConvert(named.Value, elementType);
}
else
{
expr = tryConvert(expr, elementType);
}
ExprFactory.AppendItemToList(expr, ref newList, ref newListTail);
}
exprArrayInit.DimensionSizes[0] = count;
exprArrayInit.OptionalArguments = newList;
ExprFactory.AppendItemToList(exprArrayInit, ref newArgs, ref newArgsTail);
}
}
private static ReadOnlySpan<PredefinedType> RgptIntOp =>
[
PredefinedType.PT_INT,
PredefinedType.PT_UINT,
PredefinedType.PT_LONG,
PredefinedType.PT_ULONG
];
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
internal CType ChooseArrayIndexType(Expr args)
{
// first, select the allowable types
foreach (PredefinedType predef in RgptIntOp)
{
CType type = GetPredefindType(predef);
foreach (Expr arg in args.ToEnumerable())
{
if (!canConvert(arg, type))
{
goto NEXTI;
}
}
return type;
NEXTI:
;
}
// Provide better error message in attempting cast to int.
return GetPredefindType(PredefinedType.PT_INT);
}
internal static void FillInArgInfoFromArgList(ArgInfos argInfo, Expr args)
{
CType[] prgtype = new CType[argInfo.carg];
argInfo.prgexpr = new List<Expr>();
int iarg = 0;
for (Expr list = args; list != null; iarg++)
{
Expr arg;
if (list is ExprList next)
{
arg = next.OptionalElement;
list = next.OptionalNextListNode;
}
else
{
arg = list;
list = null;
}
Debug.Assert(arg != null);
Debug.Assert(arg.Type != null);
prgtype[iarg] = arg.Type;
argInfo.prgexpr.Add(arg);
}
Debug.Assert(iarg <= argInfo.carg);
argInfo.types = TypeArray.Allocate(prgtype);
}
private static bool TryGetExpandedParams(TypeArray @params, int count, out TypeArray ppExpandedParams)
{
CType[] prgtype;
if (count < @params.Count - 1)
{
// The user has specified less arguments than our parameters, but we still
// need to return our set of types without the param array. This is in the
// case that all the parameters are optional.
prgtype = new CType[@params.Count - 1];
@params.CopyItems(0, @params.Count - 1, prgtype);
ppExpandedParams = TypeArray.Allocate(prgtype);
return true;
}
prgtype = new CType[count];
@params.CopyItems(0, @params.Count - 1, prgtype);
CType type = @params[@params.Count - 1];
if (!(type is ArrayType arr))
{
ppExpandedParams = null;
// If we don't have an array sym, we don't have expanded parameters.
return false;
}
// At this point, we have an array sym.
CType elementType = arr.ElementType;
for (int itype = @params.Count - 1; itype < count; itype++)
{
prgtype[itype] = elementType;
}
ppExpandedParams = TypeArray.Allocate(prgtype);
return true;
}
// Is the method/property callable. Not if it's an override or not user-callable.
public static bool IsMethPropCallable(MethodOrPropertySymbol sym, bool requireUC)
{
// The hide-by-pName option for binding other languages takes precedence over general
// rules of not binding to overrides.
return (!sym.isOverride || sym.isHideByName) && (!requireUC || sym.isUserCallable());
}
private static bool IsConvInTable(List<UdConvInfo> convTable, MethodSymbol meth, AggregateType ats, bool fSrc, bool fDst)
{
foreach (UdConvInfo conv in convTable)
{
if (conv.Meth.Meth() == meth &&
conv.Meth.GetType() == ats &&
conv.SrcImplicit == fSrc &&
conv.DstImplicit == fDst)
{
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// Check to see if an integral constant is within range of a integral
// destination type.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static bool isConstantInRange(ExprConstant exprSrc, CType typeDest)
{
return isConstantInRange(exprSrc, typeDest, false);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static bool isConstantInRange(ExprConstant exprSrc, CType typeDest, bool realsOk)
{
FUNDTYPE ftSrc = exprSrc.Type.FundamentalType;
FUNDTYPE ftDest = typeDest.FundamentalType;
if (ftSrc > FUNDTYPE.FT_LASTINTEGRAL || ftDest > FUNDTYPE.FT_LASTINTEGRAL)
{
if (!realsOk)
{
return false;
}
else if (ftSrc > FUNDTYPE.FT_LASTNUMERIC || ftDest > FUNDTYPE.FT_LASTNUMERIC)
{
return false;
}
}
// if converting to a float type, this always succeeds...
if (ftDest > FUNDTYPE.FT_LASTINTEGRAL)
{
return true;
}
// if converting from float to an integral type, we need to check whether it fits
if (ftSrc > FUNDTYPE.FT_LASTINTEGRAL)
{
double dvalue = exprSrc.Val.DoubleVal;
switch (ftDest)
{
case FUNDTYPE.FT_I1:
if (dvalue > -0x81 && dvalue < 0x80)
return true;
break;
case FUNDTYPE.FT_I2:
if (dvalue > -0x8001 && dvalue < 0x8000)
return true;
break;
case FUNDTYPE.FT_I4:
if (dvalue > I64(-0x80000001) && dvalue < I64(0x80000000))
return true;
break;
case FUNDTYPE.FT_I8:
// 0x7FFFFFFFFFFFFFFFFFFF is rounded to 0x800000000000000000000 in 64 bit double precision
// floating point representation. The conversion back to ulong is not possible.
if (dvalue >= -9223372036854775808.0 && dvalue < 9223372036854775808.0)
{
return true;
}
break;
case FUNDTYPE.FT_U1:
if (dvalue > -1 && dvalue < 0x100)
return true;
break;
case FUNDTYPE.FT_U2:
if (dvalue > -1 && dvalue < 0x10000)
return true;
break;
case FUNDTYPE.FT_U4:
if (dvalue > -1 && dvalue < I64(0x100000000))
return true;
break;
case FUNDTYPE.FT_U8:
// 0xFFFFFFFFFFFFFFFFFFFF is rounded to 0x100000000000000000000 in 64 bit double precision
// floating point representation. The conversion back to ulong is not possible.
if (dvalue > -1.0 && dvalue < 18446744073709551616.0)
{
return true;
}
break;
default:
break;
}
return false;
}
// U8 src is unsigned, so deal with values > MAX_LONG here.
if (ftSrc == FUNDTYPE.FT_U8)
{
ulong value = exprSrc.UInt64Value;
switch (ftDest)
{
case FUNDTYPE.FT_I1:
if (value <= (ulong)sbyte.MaxValue)
return true;
break;
case FUNDTYPE.FT_I2:
if (value <= (ulong)short.MaxValue)
return true;
break;
case FUNDTYPE.FT_I4:
if (value <= int.MaxValue)
return true;
break;
case FUNDTYPE.FT_I8:
if (value <= long.MaxValue)
return true;
break;
case FUNDTYPE.FT_U1:
if (value <= byte.MaxValue)
return true;
break;
case FUNDTYPE.FT_U2:
if (value <= ushort.MaxValue)
return true;
break;
case FUNDTYPE.FT_U4:
if (value <= uint.MaxValue)
return true;
break;
case FUNDTYPE.FT_U8:
return true;
default:
break;
}
}
else
{
long value = exprSrc.Int64Value;
switch (ftDest)
{
case FUNDTYPE.FT_I1:
if (value >= -128 && value <= 127)
return true;
break;
case FUNDTYPE.FT_I2:
if (value >= -0x8000 && value <= 0x7fff)
return true;
break;
case FUNDTYPE.FT_I4:
if (value >= I64(-0x80000000) && value <= I64(0x7fffffff))
return true;
break;
case FUNDTYPE.FT_I8:
return true;
case FUNDTYPE.FT_U1:
if (value >= 0 && value <= 0xff)
return true;
break;
case FUNDTYPE.FT_U2:
if (value >= 0 && value <= 0xffff)
return true;
break;
case FUNDTYPE.FT_U4:
if (value >= 0 && value <= I64(0xffffffff))
return true;
break;
case FUNDTYPE.FT_U8:
if (value >= 0)
return true;
break;
default:
break;
}
}
return false;
}
private static ReadOnlySpan<PredefinedName> EK2NAME =>
[
PredefinedName.PN_OPEQUALS,
PredefinedName.PN_OPCOMPARE,
PredefinedName.PN_OPTRUE,
PredefinedName.PN_OPFALSE,
PredefinedName.PN_OPINCREMENT,
PredefinedName.PN_OPDECREMENT,
PredefinedName.PN_OPNEGATION,
PredefinedName.PN_OPEQUALITY,
PredefinedName.PN_OPINEQUALITY,
PredefinedName.PN_OPLESSTHAN,
PredefinedName.PN_OPLESSTHANOREQUAL,
PredefinedName.PN_OPGREATERTHAN,
PredefinedName.PN_OPGREATERTHANOREQUAL,
PredefinedName.PN_OPPLUS,
PredefinedName.PN_OPMINUS,
PredefinedName.PN_OPMULTIPLY,
PredefinedName.PN_OPDIVISION,
PredefinedName.PN_OPMODULUS,
PredefinedName.PN_OPUNARYMINUS,
PredefinedName.PN_OPUNARYPLUS,
PredefinedName.PN_OPBITWISEAND,
PredefinedName.PN_OPBITWISEOR,
PredefinedName.PN_OPXOR,
PredefinedName.PN_OPCOMPLEMENT,
PredefinedName.PN_OPLEFTSHIFT,
PredefinedName.PN_OPRIGHTSHIFT,
];
private static Name ExpressionKindName(ExpressionKind ek)
{
Debug.Assert(ek >= ExpressionKind.FirstOp && (ek - ExpressionKind.FirstOp) < (int)EK2NAME.Length);
return NameManager.GetPredefinedName(EK2NAME[ek - ExpressionKind.FirstOp]);
}
private static void CheckUnsafe(CType type)
{
if (type == null || type.IsUnsafe())
{
throw ErrorHandling.Error(ErrorCode.ERR_UnsafeNeeded);
}
}
private AggregateSymbol ContextForMemberLookup => Context.ContextForMemberLookup;
private static readonly int[] s_zero = new[] { 0 };
private static ExprWrap WrapShortLivedExpression(Expr expr) => ExprFactory.CreateWrap(expr);
private static ExprAssignment GenerateOptimizedAssignment(Expr op1, Expr op2) => ExprFactory.CreateAssignment(op1, op2);
internal static int CountArguments(Expr args)
{
int carg = 0;
for (Expr list = args; list != null; carg++)
{
Expr arg;
if (list is ExprList next)
{
arg = next.OptionalElement;
list = next.OptionalNextListNode;
}
else
{
arg = list;
list = null;
}
Debug.Assert(arg != null);
}
return carg;
}
}
}
|