public ExprBinOp CreateBinop(ExpressionKind exprKind, CType type, Expr left, Expr right) => new ExprBinOp(exprKind, type, left, right);
/*************************************************************************************************** * Called by BindImplicitConversion when the destination type is Nullable<T>. The following * conversions are handled by this method: * * For S in { object, ValueType, interfaces implemented by underlying type} there is an explicit * unboxing conversion S => T? * System.Enum => T? there is an unboxing conversion if T is an enum type * null => T? implemented as default(T?) * * Implicit T?* => T?+ implemented by either wrapping or calling GetValueOrDefault the * appropriate number of times. * If imp/exp S => T then imp/exp S => T?+ implemented by converting to T then wrapping the * appropriate number of times. * If imp/exp S => T then imp/exp S?+ => T?+ implemented by calling GetValueOrDefault (m-1) times * then calling HasValue, producing a null if it returns false, otherwise calling Value, * converting to T then wrapping the appropriate number of times. * * The 3 rules above can be summarized with the following recursive rules: * * If imp/exp S => T? then imp/exp S? => T? implemented as * qs.HasValue ? (T?)(qs.Value) : default(T?) * If imp/exp S => T then imp/exp S => T? implemented as new T?((T)s) * * This method also handles calling bindUserDefinedConverion. This method does NOT handle * the following conversions: * * Implicit boxing conversion from S? to { object, ValueType, Enum, ifaces implemented by S }. (Handled by BindImplicitConversion.) * If imp/exp S => T then explicit S?+ => T implemented by calling Value the appropriate number * of times. (Handled by BindExplicitConversion.) * * The recursive equivalent is: * * If imp/exp S => T and T is not nullable then explicit S? => T implemented as qs.Value * * Some nullable conversion are NOT standard conversions. In particular, if S => T is implicit * then S? => T is not standard. Similarly if S => T is not implicit then S => T? is not standard. ***************************************************************************************************/ private bool BindNubConversion(NullableType nubDst) { // This code assumes that STANDARD and ISEXPLICIT are never both set. // bindUserDefinedConversion should ensure this! Debug.Assert(0 != (~_flags & (CONVERTTYPE.STANDARD | CONVERTTYPE.ISEXPLICIT))); Debug.Assert(_exprSrc == null || _exprSrc.Type == _typeSrc); Debug.Assert(!_needsExprDest || _exprSrc != null); Debug.Assert(_typeSrc != nubDst); // BindImplicitConversion should have taken care of this already. AggregateType atsDst = nubDst.GetAts(GetErrorContext()); if (atsDst == null) { return(false); } // Check for the unboxing conversion. This takes precedence over the wrapping conversions. if (GetSymbolLoader().HasBaseConversion(nubDst.GetUnderlyingType(), _typeSrc) && !CConversions.FWrappingConv(_typeSrc, nubDst)) { // These should be different! Fix the caller if typeSrc is an AggregateType of Nullable. Debug.Assert(atsDst != _typeSrc); // typeSrc is a base type of the destination nullable type so there is an explicit // unboxing conversion. if (0 == (_flags & CONVERTTYPE.ISEXPLICIT)) { return(false); } if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _exprTypeDest, out _exprDest, EXPRFLAG.EXF_UNBOX); } return(true); } int cnubDst; int cnubSrc; CType typeDstBase = nubDst.StripNubs(out cnubDst); ExprClass exprTypeDstBase = GetExprFactory().MakeClass(typeDstBase); CType typeSrcBase = _typeSrc.StripNubs(out cnubSrc); ConversionFunc pfn = (_flags & CONVERTTYPE.ISEXPLICIT) != 0 ? (ConversionFunc)_binder.BindExplicitConversion : (ConversionFunc)_binder.BindImplicitConversion; if (cnubSrc == 0) { Debug.Assert(_typeSrc == typeSrcBase); // The null type can be implicitly converted to T? as the default value. if (_typeSrc.IsNullType()) { // If we have the constant null, generate it as a default value of T?. If we have // some crazy expression which has been determined to be always null, like (null??null) // keep it in its expression form and transform it in the nullable rewrite pass. if (_needsExprDest) { if (_exprSrc.isCONSTANT_OK()) { _exprDest = GetExprFactory().CreateZeroInit(nubDst); } else { _exprDest = GetExprFactory().CreateCast(0x00, _typeDest, _exprSrc); } } return(true); } Expr exprTmp = _exprSrc; // If there is an implicit/explicit S => T then there is an implicit/explicit S => T? if (_typeSrc == typeDstBase || pfn(_exprSrc, _typeSrc, exprTypeDstBase, nubDst, _needsExprDest, out exprTmp, _flags | CONVERTTYPE.NOUDC)) { if (_needsExprDest) { ExprUserDefinedConversion exprUDC = exprTmp as ExprUserDefinedConversion; if (exprUDC != null) { exprTmp = exprUDC.UserDefinedCall; } // This logic is left over from the days when T?? was legal. However there are error/LAF cases that necessitates the loop. // typeSrc is not nullable so just wrap the required number of times. For legal code (cnubDst <= 0). for (int i = 0; i < cnubDst; i++) { ExprCall call = _binder.BindNubNew(exprTmp); exprTmp = call; call.NullableCallLiftKind = NullableCallLiftKind.NullableConversionConstructor; } if (exprUDC != null) { exprUDC.UserDefinedCall = exprTmp; exprUDC.Type = (CType)exprTmp.Type; exprTmp = exprUDC; } Debug.Assert(exprTmp.Type == nubDst); _exprDest = exprTmp; } return(true); } // No builtin conversion. Maybe there is a user defined conversion.... return(0 == (_flags & CONVERTTYPE.NOUDC) && _binder.bindUserDefinedConversion(_exprSrc, _typeSrc, nubDst, _needsExprDest, out _exprDest, 0 == (_flags & CONVERTTYPE.ISEXPLICIT))); } // Both are Nullable so there is only a conversion if there is a conversion between the base types. // That is, if there is an implicit/explicit S => T then there is an implicit/explicit S?+ => T?+. if (typeSrcBase != typeDstBase && !pfn(null, typeSrcBase, exprTypeDstBase, nubDst, false, out _exprDest, _flags | CONVERTTYPE.NOUDC)) { // No builtin conversion. Maybe there is a user defined conversion.... return(0 == (_flags & CONVERTTYPE.NOUDC) && _binder.bindUserDefinedConversion(_exprSrc, _typeSrc, nubDst, _needsExprDest, out _exprDest, 0 == (_flags & CONVERTTYPE.ISEXPLICIT))); } if (_needsExprDest) { MethWithInst mwi = new MethWithInst(null, null); ExprMemberGroup pMemGroup = GetExprFactory().CreateMemGroup(null, mwi); ExprCall exprDst = GetExprFactory().CreateCall(0, nubDst, _exprSrc, pMemGroup, null); // Here we want to first check whether or not the conversions work on the base types. Expr arg1 = _binder.mustCast(_exprSrc, typeSrcBase); ExprClass arg2 = GetExprFactory().MakeClass(typeDstBase); bool convertible; if (0 != (_flags & CONVERTTYPE.ISEXPLICIT)) { convertible = _binder.BindExplicitConversion(arg1, arg1.Type, arg2, typeDstBase, out arg1, _flags | CONVERTTYPE.NOUDC); } else { convertible = _binder.BindImplicitConversion(arg1, arg1.Type, arg2, typeDstBase, out arg1, _flags | CONVERTTYPE.NOUDC); } if (!convertible) { VSFAIL("bind(Im|Ex)plicitConversion failed unexpectedly"); return(false); } exprDst.CastOfNonLiftedResultToLiftedType = _binder.mustCast(arg1, nubDst, 0); exprDst.NullableCallLiftKind = NullableCallLiftKind.NullableConversion; exprDst.PConversions = exprDst.CastOfNonLiftedResultToLiftedType; _exprDest = exprDst; } return(true); }
public ExprMemberGroup(EXPRFLAG flags, Name name, TypeArray typeArgs, SYMKIND symKind, CType parentType, MethodOrPropertySymbol pMPS, Expr optionalObject, CMemberLookupResults memberLookupResults) : base(ExpressionKind.MemberGroup, MethodGroupType.Instance) { Debug.Assert( (flags & ~(EXPRFLAG.EXF_CTOR | EXPRFLAG.EXF_INDEXER | EXPRFLAG.EXF_OPERATOR | EXPRFLAG.EXF_NEWOBJCALL | EXPRFLAG.EXF_DELEGATE | EXPRFLAG.EXF_USERCALLABLE | EXPRFLAG.EXF_MASK_ANY)) == 0); Flags = flags; Name = name; TypeArgs = typeArgs ?? BSYMMGR.EmptyTypeArray(); SymKind = symKind; ParentType = parentType; OptionalObject = optionalObject; MemberLookupResults = memberLookupResults; }
public ExprUserLogicalOp CreateUserLogOp(CType type, Expr trueFalseCall, ExprCall operatorCall) => new ExprUserLogicalOp(type, trueFalseCall, operatorCall);
public ExprMulti CreateMulti(EXPRFLAG flags, CType type, Expr left, Expr op) => new ExprMulti(type, flags, left, op);
public ExprUserDefinedConversion CreateUserDefinedConversion(Expr arg, Expr call, MethWithInst method) => new ExprUserDefinedConversion(arg, call, method);
public ExprCast CreateCast(EXPRFLAG flags, CType type, Expr argument) => new ExprCast(flags, type, argument);
public ExprAssignment CreateAssignment(Expr left, Expr right) => new ExprAssignment(left, right);
//////////////////////////////////////////////////////////////////////////////// public ExprNamedArgumentSpecification CreateNamedArgumentSpecification(Name name, Expr value) => new ExprNamedArgumentSpecification(name, value);
// The call may be lifted, but we do not mark the outer binop as lifted. public ExprUnaryOp CreateUserDefinedUnaryOperator(ExpressionKind exprKind, CType type, Expr operand, ExprCall call, MethPropWithInst userMethod) => new ExprUnaryOp(exprKind, type, operand, call, userMethod);
//////////////////////////////////////////////////////////////////////////////// // Create a node that evaluates the first, evaluates the second, results in the first. public ExprBinOp CreateReverseSequence(Expr first, Expr second) => CreateBinop(ExpressionKind.SequenceReverse, first.Type, first, second);
public ExprBinOp CreateUserDefinedBinop(ExpressionKind exprKind, CType type, Expr left, Expr right, Expr call, MethPropWithInst userMethod) => new ExprBinOp(exprKind, type, left, right, call, userMethod);
public ExprOperator CreateOperator(ExpressionKind exprKind, CType type, Expr arg1, Expr arg2) { Debug.Assert(arg1 != null); Debug.Assert(exprKind.IsUnaryOperator() == (arg2 == null)); return(exprKind.IsUnaryOperator() ? (ExprOperator)CreateUnaryOp(exprKind, type, arg1) : CreateBinop(exprKind, type, arg1, arg2)); }
public ExprUnaryOp CreateUnaryOp(ExpressionKind exprKind, CType type, Expr operand) => new ExprUnaryOp(exprKind, type, operand);
public ExprProperty CreateProperty(CType type, Expr optionalObjectThrough, Expr arguments, ExprMemberGroup memberGroup, PropWithType property, MethWithType setMethod) => new ExprProperty(type, optionalObjectThrough, arguments, memberGroup, property, setMethod);
public ExprWrap CreateWrap(Expr expression) => new ExprWrap(expression);
public ExprMemberGroup CreateMemGroup(EXPRFLAG flags, Name name, TypeArray typeArgs, SYMKIND symKind, CType parentType, MethodOrPropertySymbol memberSymbol, Expr obj, CMemberLookupResults memberLookupResults) => new ExprMemberGroup(Types.GetMethGrpType(), flags, name, typeArgs, symKind, parentType, memberSymbol, obj, memberLookupResults);
public ExprCall CreateCall(EXPRFLAG flags, CType type, Expr arguments, ExprMemberGroup memberGroup, MethWithInst method) => new ExprCall(type, flags, arguments, memberGroup, method);
public ExprCast CreateCast(CType type, Expr argument) => CreateCast(0, type, argument);
public ExprList CreateList(Expr op1, Expr op2) => new ExprList(op1, op2);
public ExprReturn CreateReturn(Expr optionalObject) => new ExprReturn(optionalObject);
public ExprList CreateList(Expr op1, Expr op2, Expr op3) => CreateList(op1, CreateList(op2, op3));
public ExprConcat CreateConcat(Expr first, Expr second) => new ExprConcat(first, second);
public ExprList CreateList(Expr op1, Expr op2, Expr op3, Expr op4) => CreateList(op1, CreateList(op2, CreateList(op3, op4)));
// Return true if we actually report a failure. private bool TryReportLvalueFailure(Expr expr, CheckLvalueKind kind) { Debug.Assert(expr != null); // We have a lvalue failure. Was the reason because this field // was marked readonly? Give special messages for this case. bool isNested = false; // Did we recurse on a field or property to give a better error? Expr walk = expr; while (true) { Debug.Assert(walk != null); if (walk.isANYLOCAL_OK()) { ReportLocalError(walk.asANYLOCAL().Local, kind, isNested); return(true); } Expr pObject = null; if (walk.isPROP()) { // We've already reported read-only-property errors. Debug.Assert(walk.asPROP().MethWithTypeSet != null); pObject = walk.asPROP().MemberGroup.OptionalObject; } else if (walk.isFIELD()) { ExprField field = walk.asFIELD(); if (field.FieldWithType.Field().isReadOnly) { ReportReadOnlyError(field, kind, isNested); return(true); } if (!field.FieldWithType.Field().isStatic) { pObject = field.OptionalObject; } } if (pObject != null && pObject.Type.isStructOrEnum()) { if (pObject.isCALL() || pObject.isPROP()) { // assigning to RHS of method or property getter returning a value-type on the stack or // passing RHS of method or property getter returning a value-type on the stack, as ref or out ErrorContext.Error(ErrorCode.ERR_ReturnNotLValue, pObject.GetSymWithType()); return(true); } if (pObject.isCAST()) { // An unboxing conversion. // // In the static compiler, we give the following error here: // ErrorContext.Error(pObject.GetTree(), ErrorCode.ERR_UnboxNotLValue); // // But in the runtime, we allow this - mark that we're doing an // unbox here, so that we gen the correct expression tree for it. pObject.Flags |= EXPRFLAG.EXF_UNBOXRUNTIME; return(false); } } // everything else if (pObject != null && !pObject.isLvalue() && (walk.isFIELD() || (!isNested && walk.isPROP()))) { Debug.Assert(pObject.Type.isStructOrEnum()); walk = pObject; } else { ErrorContext.Error(GetStandardLvalueError(kind)); return(true); } isNested = true; } }
public ExprField CreateField(CType type, Expr optionalObject, FieldWithType field, bool isLValue) => new ExprField(type, optionalObject, field, isLValue);
/* * BindImplicitConversion * * This is a complex routine with complex parameters. Generally, this should * be called through one of the helper methods that insulates you * from the complexity of the interface. This routine handles all the logic * associated with implicit conversions. * * exprSrc - the expression being converted. Can be null if only type conversion * info is being supplied. * typeSrc - type of the source * typeDest - type of the destination * exprDest - returns an expression of the src converted to the dest. If null, we * only care about whether the conversion can be attempted, not the * expression tree. * flags - flags possibly customizing the conversions allowed. E.g., can suppress * user-defined conversions. * * returns true if the conversion can be made, false if not. */ public bool Bind() { // 13.1 Implicit conversions // // The following conversions are classified as implicit conversions: // // * Identity conversions // * Implicit numeric conversions // * Implicit enumeration conversions // * Implicit reference conversions // * Boxing conversions // * Implicit type parameter conversions // * Implicit constant expression conversions // * User-defined implicit conversions // * Implicit conversions from an anonymous method expression to a compatible delegate type // * Implicit conversion from a method group to a compatible delegate type // * Conversions from the null type (11.2.7) to any nullable type // * Implicit nullable conversions // * Lifted user-defined implicit conversions // // Implicit conversions can occur in a variety of situations, including function member invocations // (14.4.3), cast expressions (14.6.6), and assignments (14.14). // Can't convert to or from the error type. if (_typeSrc == null || _typeDest == null || _typeDest.IsNeverSameType()) { return(false); } Debug.Assert(_typeSrc != null && _typeDest != null); // types must be supplied. Debug.Assert(_exprSrc == null || _typeSrc == _exprSrc.Type); // type of source should be correct if source supplied Debug.Assert(!_needsExprDest || _exprSrc != null); // need source expr to create dest expr switch (_typeDest.GetTypeKind()) { case TypeKind.TK_ErrorType: Debug.Assert(_typeDest.AsErrorType().HasTypeParent() || _typeDest.AsErrorType().HasNSParent()); if (_typeSrc != _typeDest) { return(false); } if (_needsExprDest) { _exprDest = _exprSrc; } return(true); case TypeKind.TK_NullType: // Can only convert to the null type if src is null. if (!_typeSrc.IsNullType()) { return(false); } if (_needsExprDest) { _exprDest = _exprSrc; } return(true); case TypeKind.TK_MethodGroupType: VSFAIL("Something is wrong with Type.IsNeverSameType()"); return(false); case TypeKind.TK_NaturalIntegerType: case TypeKind.TK_ArgumentListType: return(_typeSrc == _typeDest); case TypeKind.TK_VoidType: return(false); default: break; } if (_typeSrc.IsErrorType()) { Debug.Assert(!_typeDest.IsErrorType()); return(false); } // 13.1.1 Identity conversion // // An identity conversion converts from any type to the same type. This conversion exists only // such that an entity that already has a required type can be said to be convertible to that type. if (_typeSrc == _typeDest && ((_flags & CONVERTTYPE.ISEXPLICIT) == 0 || (!_typeSrc.isPredefType(PredefinedType.PT_FLOAT) && !_typeSrc.isPredefType(PredefinedType.PT_DOUBLE)))) { if (_needsExprDest) { _exprDest = _exprSrc; } return(true); } if (_typeDest.IsNullableType()) { return(BindNubConversion(_typeDest.AsNullableType())); } if (_typeSrc.IsNullableType()) { return(bindImplicitConversionFromNullable(_typeSrc.AsNullableType())); } if ((_flags & CONVERTTYPE.ISEXPLICIT) != 0) { _flags |= CONVERTTYPE.NOUDC; } // Get the fundamental types of destination. FUNDTYPE ftDest = _typeDest.fundType(); Debug.Assert(ftDest != FUNDTYPE.FT_NONE || _typeDest.IsParameterModifierType()); switch (_typeSrc.GetTypeKind()) { default: VSFAIL("Bad type symbol kind"); break; case TypeKind.TK_MethodGroupType: if (_exprSrc is ExprMemberGroup memGrp) { ExprCall outExpr; bool retVal = _binder.BindGrpConversion(memGrp, _typeDest, _needsExprDest, out outExpr, false); _exprDest = outExpr; return(retVal); } return(false); case TypeKind.TK_VoidType: case TypeKind.TK_ErrorType: case TypeKind.TK_ParameterModifierType: case TypeKind.TK_ArgumentListType: return(false); case TypeKind.TK_NullType: if (bindImplicitConversionFromNull()) { return(true); } // If not, try user defined implicit conversions. break; case TypeKind.TK_ArrayType: if (bindImplicitConversionFromArray()) { return(true); } // If not, try user defined implicit conversions. break; case TypeKind.TK_PointerType: if (bindImplicitConversionFromPointer()) { return(true); } // If not, try user defined implicit conversions. break; case TypeKind.TK_TypeParameterType: if (bindImplicitConversionFromTypeVar(_typeSrc.AsTypeParameterType())) { return(true); } // If not, try user defined implicit conversions. break; case TypeKind.TK_AggregateType: // TypeReference and ArgIterator can't be boxed (or converted to anything else) if (_typeSrc.isSpecialByRefType()) { return(false); } if (bindImplicitConversionFromAgg(_typeSrc.AsAggregateType())) { return(true); } // If not, try user defined implicit conversions. break; } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // RUNTIME BINDER ONLY CHANGE // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // Every incoming dynamic operand should be implicitly convertible // to any type that it is an instance of. object srcRuntimeObject = _exprSrc?.RuntimeObject; if (srcRuntimeObject != null && _typeDest.AssociatedSystemType.IsInstanceOfType(srcRuntimeObject) && _binder.GetSemanticChecker().CheckTypeAccess(_typeDest, _binder.Context.ContextForMemberLookup())) { if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _exprTypeDest, out _exprDest, _exprSrc.Flags & EXPRFLAG.EXF_CANTBENULL); } return(true); } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // END RUNTIME BINDER ONLY CHANGE // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // 13.1.8 User-defined implicit conversions // // A user-defined implicit conversion consists of an optional standard implicit conversion, // followed by execution of a user-defined implicit conversion operator, followed by another // optional standard implicit conversion. The exact rules for evaluating user-defined // conversions are described in 13.4.3. if (0 == (_flags & CONVERTTYPE.NOUDC)) { return(_binder.bindUserDefinedConversion(_exprSrc, _typeSrc, _typeDest, _needsExprDest, out _exprDest, true)); } // No conversion was found. return(false); }
public ExprArrayInit CreateArrayInit(CType type, Expr arguments, Expr argumentDimensions, int[] dimSizes, int dimSize) => new ExprArrayInit(type, arguments, argumentDimensions, dimSizes, dimSize);
/*************************************************************************************************** * Lookup must be called before anything else can be called. * * typeSrc - Must be an AggregateType or TypeParameterType. * obj - the expression through which the member is being accessed. This is used for accessibility * of protected members and for constructing a MEMGRP from the results of the lookup. * It is legal for obj to be an EK_CLASS, in which case it may be used for accessibility, but * will not be used for MEMGRP construction. * symWhere - the symbol from with the name is being accessed (for checking accessibility). * name - the name to look for. * arity - the number of type args specified. Only members that support this arity are found. * Note that when arity is zero, all methods are considered since we do type argument * inferencing. * * flags - See MemLookFlags. * TypeVarsAllowed only applies to the most derived type (not base types). ***************************************************************************************************/ public bool Lookup(CSemanticChecker checker, CType typeSrc, Expr obj, ParentSymbol symWhere, Name name, int arity, MemLookFlags flags) { Debug.Assert((flags & ~MemLookFlags.All) == 0); Debug.Assert(obj == null || obj.Type != null); Debug.Assert(typeSrc is AggregateType); Debug.Assert(checker != null); _prgtype = _rgtypeStart; // Save the inputs for error handling, etc. _pSemanticChecker = checker; _pSymbolLoader = checker.SymbolLoader; _typeSrc = typeSrc; _symWhere = symWhere; _name = name; _arity = arity; _flags = flags; _typeQual = (_flags & MemLookFlags.Ctor) != 0 ? _typeSrc : obj?.Type; // Determine what to search. AggregateType typeCls1 = null; AggregateType typeIface = null; TypeArray ifaces = BSYMMGR.EmptyTypeArray(); AggregateType typeCls2 = null; if (!typeSrc.IsInterfaceType) { typeCls1 = (AggregateType)typeSrc; if (typeCls1.IsWindowsRuntimeType) { ifaces = typeCls1.GetWinRTCollectionIfacesAll(GetSymbolLoader()); } } else { Debug.Assert((_flags & (MemLookFlags.Ctor | MemLookFlags.NewObj | MemLookFlags.Operator | MemLookFlags.BaseCall)) == 0); typeIface = (AggregateType)typeSrc; ifaces = typeIface.IfacesAll; } if (typeIface != null || ifaces.Count > 0) { typeCls2 = GetSymbolLoader().GetPredefindType(PredefinedType.PT_OBJECT); } // Search the class first (except possibly object). if (typeCls1 == null || LookupInClass(typeCls1, ref typeCls2)) { // Search the interfaces. if ((typeIface != null || ifaces.Count > 0) && LookupInInterfaces(typeIface, ifaces) && typeCls2 != null) { // Search object last. Debug.Assert(typeCls2 != null && typeCls2.IsPredefType(PredefinedType.PT_OBJECT)); AggregateType result = null; LookupInClass(typeCls2, ref result); } } return(!FError()); }
public ExprArrayIndex CreateArrayIndex(CType type, Expr array, Expr index) => new ExprArrayIndex(type, array, index);