mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-09 03:52:19 +02:00
Added support for C-style vararg methods
This commit is contained in:
parent
89e6b0d577
commit
7741344fd2
16 changed files with 129 additions and 68 deletions
|
@ -202,7 +202,7 @@ class BeFunctionType : public BeType
|
|||
public:
|
||||
String mName;
|
||||
BeType* mReturnType;
|
||||
std::vector<BeFunctionTypeParam> mParams;
|
||||
Array<BeFunctionTypeParam> mParams;
|
||||
bool mIsVarArg;
|
||||
|
||||
virtual void HashContent(BeHashContext& hashCtx) override
|
||||
|
|
|
@ -1960,6 +1960,14 @@ String BeModule::ToString(BeFunction* wantFunc)
|
|||
dc.mSeenNames[param.mName] = 0;
|
||||
str += "%" + param.mName;
|
||||
}
|
||||
|
||||
if (funcType->mIsVarArg)
|
||||
{
|
||||
if (!funcType->mParams.IsEmpty())
|
||||
str += ", ";
|
||||
str += "...";
|
||||
}
|
||||
|
||||
str += ")";
|
||||
|
||||
if (func->mAlwaysInline)
|
||||
|
@ -2917,6 +2925,14 @@ void BeModule::ToString(StringImpl& str, BeType* type)
|
|||
str += ", ";
|
||||
ToString(str, funcType->mParams[paramIdx].mType);
|
||||
}
|
||||
|
||||
if (funcType->mIsVarArg)
|
||||
{
|
||||
if (!funcType->mParams.IsEmpty())
|
||||
str += ", ";
|
||||
str += "...";
|
||||
}
|
||||
|
||||
str += ")";
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ void BfStructuralVisitor::Visit(BfQualifiedTypeReference* qualifiedTypeRef)
|
|||
Visit(qualifiedTypeRef->ToBase());
|
||||
}
|
||||
|
||||
void BfStructuralVisitor::Visit(BfDotTypeReference * typeRef)
|
||||
void BfStructuralVisitor::Visit(BfDotTypeReference* typeRef)
|
||||
{
|
||||
Visit(typeRef->ToBase());
|
||||
}
|
||||
|
@ -1411,6 +1411,8 @@ const char* Beefy::BfTokenToString(BfToken token)
|
|||
return ".";
|
||||
case BfToken_DotDot:
|
||||
return "..";
|
||||
case BfToken_DotDotDot:
|
||||
return "...";
|
||||
case BfToken_QuestionDot:
|
||||
return "?.";
|
||||
case BfToken_QuestionLBracket:
|
||||
|
|
|
@ -215,6 +215,7 @@ enum BfToken : uint8
|
|||
BfToken_Comma,
|
||||
BfToken_Dot,
|
||||
BfToken_DotDot,
|
||||
BfToken_DotDotDot,
|
||||
BfToken_QuestionDot,
|
||||
BfToken_QuestionLBracket,
|
||||
BfToken_AutocompleteDot,
|
||||
|
|
|
@ -597,7 +597,7 @@ BfMethodDef* BfDefBuilder::CreateMethodDef(BfMethodDeclaration* methodDeclaratio
|
|||
bool didDefaultsError = false;
|
||||
bool hadParams = false;
|
||||
bool hasDefault = false;
|
||||
for (int paramIdx = 0; paramIdx < (int) methodDeclaration->mParams.size(); paramIdx++)
|
||||
for (int paramIdx = 0; paramIdx < (int)methodDeclaration->mParams.size(); paramIdx++)
|
||||
{
|
||||
BfParameterDeclaration* paramDecl = methodDeclaration->mParams[paramIdx];
|
||||
|
||||
|
@ -612,6 +612,17 @@ BfMethodDef* BfDefBuilder::CreateMethodDef(BfMethodDeclaration* methodDeclaratio
|
|||
else //
|
||||
paramDef->mParamKind = BfParamKind_Params;
|
||||
|
||||
if (auto dotTypeRef = BfNodeDynCast<BfDotTypeReference>(paramDef->mTypeRef))
|
||||
{
|
||||
if (dotTypeRef->mDotToken->mToken == BfToken_DotDotDot)
|
||||
{
|
||||
if (paramIdx == (int)methodDeclaration->mParams.size() - 1)
|
||||
paramDef->mParamKind = BfParamKind_VarArgs;
|
||||
else
|
||||
Fail("Varargs specifier must be the last parameter", methodDef->mParams[paramIdx - 1]->mParamDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
if (paramDef->mParamDeclaration != NULL)
|
||||
{
|
||||
if (paramDef->mParamKind == BfParamKind_Params)
|
||||
|
|
|
@ -1435,10 +1435,10 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* typeInstance, BfMethodDef* che
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: Does this ever get hit?
|
||||
// Not enough arguments?
|
||||
// Too many arguments (not all incoming arguments processed)
|
||||
if (argIdx < (int)mArguments.size())
|
||||
{
|
||||
if (!methodInstance->IsVarArgs())
|
||||
goto NoMatch;
|
||||
}
|
||||
|
||||
|
@ -4431,8 +4431,13 @@ BfTypedValue BfExprEvaluator::CreateCall(BfMethodInstance* methodInstance, BfIRV
|
|||
argIdx++;
|
||||
}
|
||||
|
||||
int paramCount = methodInstance->GetParamCount();
|
||||
|
||||
for ( ; argIdx < (int)irArgs.size(); /*argIdx++*/)
|
||||
{
|
||||
if (argIdx >= paramCount)
|
||||
break;
|
||||
|
||||
auto _HandleParamType = [&] (BfType* paramType)
|
||||
{
|
||||
if (paramType->IsStruct())
|
||||
|
@ -4986,6 +4991,31 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
|
||||
if (paramIdx >= (int)methodInstance->GetParamCount())
|
||||
{
|
||||
if (methodInstance->IsVarArgs())
|
||||
{
|
||||
if (argIdx >= (int)argValues.size())
|
||||
break;
|
||||
|
||||
BfTypedValue argValue = ResolveArgValue(argValues[argIdx], NULL);
|
||||
if (argValue)
|
||||
{
|
||||
auto typeInst = argValue.mType->ToTypeInstance();
|
||||
|
||||
if (argValue.mType == mModule->GetPrimitiveType(BfTypeCode_Single))
|
||||
argValue = mModule->Cast(argValues[argIdx].mExpression, argValue, mModule->GetPrimitiveType(BfTypeCode_Double));
|
||||
|
||||
if ((typeInst != NULL) && (typeInst->mTypeDef == mModule->mCompiler->mStringTypeDef))
|
||||
{
|
||||
BfType* charType = mModule->GetPrimitiveType(BfTypeCode_Char8);
|
||||
BfType* charPtrType = mModule->CreatePointerType(charType);
|
||||
argValue = mModule->Cast(argValues[argIdx].mExpression, argValue, charPtrType);
|
||||
}
|
||||
PushArg(argValue, irArgs, true, false);
|
||||
}
|
||||
argIdx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argIdx < (int)argValues.size())
|
||||
{
|
||||
BfAstNode* errorRef = argValues[argIdx].mExpression;
|
||||
|
@ -5632,41 +5662,10 @@ BfTypedValue BfExprEvaluator::ResolveArgValue(BfResolvedArg& resolvedArg, BfType
|
|||
BfEvalExprFlags flags = BfEvalExprFlags_NoCast;
|
||||
if ((paramKind == BfParamKind_Params) || (paramKind == BfParamKind_DelegateParam))
|
||||
flags = (BfEvalExprFlags)(flags | BfEvalExprFlags_AllowParamsExpr);
|
||||
// if ((mModule->mCurMethodInstance->mHadGenericDelegateParams) && (!mModule->mCurMethodInstance->mIsUnspecialized))
|
||||
// flags = (BfEvalExprFlags)(flags | BfEvalExprFlags_AllowParamsExpr);
|
||||
|
||||
argValue = mModule->CreateValueFromExpression(exprEvaluator, expr, wantType, flags);
|
||||
|
||||
// if ((resolvedArg.mArgFlags & BfArgFlag_ParamsExpr) != 0)
|
||||
// {
|
||||
// if ((mModule->mCurMethodState != NULL) && (exprEvaluator.mResultLocalVar != NULL) && (exprEvaluator.mResultLocalVarRefNode != NULL))
|
||||
// {
|
||||
// auto localVar = exprEvaluator.mResultLocalVar;
|
||||
// int fieldIdx = mResultLocalVarField - 1;
|
||||
// auto methodState = mModule->mCurMethodState->GetMethodStateForLocal(localVar);
|
||||
// if (localVar->mCompositeCount >= 0)
|
||||
// {
|
||||
// for (int compositeIdx = 0; compositeIdx < localVar->mCompositeCount; compositeIdx++)
|
||||
// {
|
||||
// BfResolvedArg compositeResolvedArg;
|
||||
// auto compositeLocalVar = methodState->mLocals[localVar->mLocalVarIdx + compositeIdx + 1];
|
||||
// auto argValue = exprEvaluator.LoadLocal(compositeLocalVar);
|
||||
// if (argValue)
|
||||
// {
|
||||
// if (argValue.mType->IsRef())
|
||||
// argValue.mKind = BfTypedValueKind_Value;
|
||||
// else if (!argValue.mType->IsStruct())
|
||||
// argValue = mModule->LoadValue(argValue, NULL, exprEvaluator.mIsVolatileReference);
|
||||
// }
|
||||
// resolvedArg.mTypedValue = argValue;
|
||||
// resolvedArg.mArgFlags = (BfArgFlags)(resolvedArg.mArgFlags | BfArgFlag_FromParamComposite);
|
||||
// return resolvedArg.mTypedValue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if ((argValue) && (argValue.mType != wantType))
|
||||
if ((argValue) && (argValue.mType != wantType) && (wantType != NULL))
|
||||
{
|
||||
if ((mDeferScopeAlloc != NULL) && (wantType == mModule->mContext->mBfObjectType))
|
||||
{
|
||||
|
@ -5686,7 +5685,7 @@ BfTypedValue BfExprEvaluator::ResolveArgValue(BfResolvedArg& resolvedArg, BfType
|
|||
auto expr = BfNodeDynCast<BfExpression>(resolvedArg.mExpression);
|
||||
BF_ASSERT(expr != NULL);
|
||||
argValue = mModule->CreateValueFromExpression(expr, wantType, (BfEvalExprFlags)(BfEvalExprFlags_NoCast | BfEvalExprFlags_AllowRefExpr | BfEvalExprFlags_AllowOutExpr));
|
||||
if (argValue)
|
||||
if ((argValue) && (wantType != NULL))
|
||||
argValue = mModule->Cast(expr, argValue, wantType);
|
||||
}
|
||||
else if ((resolvedArg.mArgFlags & (BfArgFlag_UntypedDefault)) != 0)
|
||||
|
@ -5748,8 +5747,6 @@ BfTypedValue BfExprEvaluator::ResolveArgValue(BfResolvedArg& resolvedArg, BfType
|
|||
CheckVariableDeclaration(resolvedArg.mExpression, false, false, false);
|
||||
|
||||
argValue = BfTypedValue(localVar->mAddr, mModule->CreateRefType(variableType, BfRefType::RefKind_Out));
|
||||
|
||||
//argValue = mModule->GetDefaultTypedValue(wantType);
|
||||
}
|
||||
return argValue;
|
||||
}
|
||||
|
|
|
@ -4257,7 +4257,7 @@ BfIRFunctionType BfIRBuilder::MapMethod(BfMethodInstance* methodInstance)
|
|||
SizedArray<BfIRType, 8> paramTypes;
|
||||
methodInstance->GetIRFunctionInfo(mModule, retType, paramTypes);
|
||||
|
||||
auto funcType = CreateFunctionType(retType, paramTypes, false);
|
||||
auto funcType = CreateFunctionType(retType, paramTypes, methodInstance->IsVarArgs());
|
||||
if (useCache)
|
||||
mMethodTypeMap[methodInstance] = funcType;
|
||||
|
||||
|
|
|
@ -8794,6 +8794,11 @@ BfIRValue BfModule::CreateFunctionFrom(BfMethodInstance* methodInstance, bool tr
|
|||
if (mCompiler->IsSkippingExtraResolveChecks())
|
||||
return BfIRValue();
|
||||
|
||||
if (methodInstance->mMethodDef->mName == "Check")
|
||||
{
|
||||
NOP;
|
||||
}
|
||||
|
||||
if (methodInstance->mMethodInstanceGroup->mOwner->IsInterface())
|
||||
{
|
||||
//BF_ASSERT(!methodInstance->mIRFunction);
|
||||
|
@ -8841,7 +8846,7 @@ BfIRValue BfModule::CreateFunctionFrom(BfMethodInstance* methodInstance, bool tr
|
|||
BfIRType returnType;
|
||||
SizedArray<BfIRType, 8> paramTypes;
|
||||
methodInstance->GetIRFunctionInfo(this, returnType, paramTypes);
|
||||
auto funcType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes);
|
||||
auto funcType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes, methodInstance->IsVarArgs());
|
||||
|
||||
auto func = mBfIRBuilder->CreateFunction(funcType, BfIRLinkageType_External, methodName);
|
||||
auto callingConv = GetIRCallingConvention(methodInstance);
|
||||
|
@ -13859,7 +13864,7 @@ BfIRValue BfModule::CreateDllImportGlobalVar(BfMethodInstance* methodInstance, b
|
|||
SizedArray<BfIRType, 8> paramTypes;
|
||||
methodInstance->GetIRFunctionInfo(this, returnType, paramTypes);
|
||||
|
||||
BfIRFunctionType externFunctionType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes, false);
|
||||
BfIRFunctionType externFunctionType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes, methodInstance->IsVarArgs());
|
||||
auto ptrType = mBfIRBuilder->GetPointerTo(externFunctionType);
|
||||
|
||||
BfIRValue initVal;
|
||||
|
@ -13907,14 +13912,14 @@ void BfModule::CreateDllImportMethod()
|
|||
|
||||
BfIRType returnType;
|
||||
SizedArray<BfIRType, 8> paramTypes;
|
||||
mCurMethodInstance->GetIRFunctionInfo(this, returnType, paramTypes);
|
||||
mCurMethodInstance->GetIRFunctionInfo(this, returnType, paramTypes, mCurMethodInstance->IsVarArgs());
|
||||
|
||||
SizedArray<BfIRValue, 8> args;
|
||||
|
||||
for (int i = 0; i < (int)paramTypes.size(); i++)
|
||||
args.push_back(mBfIRBuilder->GetArgument(i));
|
||||
|
||||
BfIRFunctionType externFunctionType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes, false);
|
||||
BfIRFunctionType externFunctionType = mBfIRBuilder->CreateFunctionType(returnType, paramTypes, mCurMethodInstance->IsVarArgs());
|
||||
|
||||
if (isHotCompile)
|
||||
{
|
||||
|
@ -18115,7 +18120,7 @@ BfModuleMethodInstance BfModule::GetLocalMethodInstance(BfLocalMethod* localMeth
|
|||
BfIRFunctionType funcType;
|
||||
auto voidType = GetPrimitiveType(BfTypeCode_None);
|
||||
SizedArray<BfIRType, 0> paramTypes;
|
||||
funcType = mBfIRBuilder->CreateFunctionType(mBfIRBuilder->MapType(voidType), paramTypes, false);
|
||||
funcType = mBfIRBuilder->CreateFunctionType(mBfIRBuilder->MapType(voidType), paramTypes, methodInstance->IsVarArgs());
|
||||
|
||||
mBfIRBuilder->SaveDebugLocation();
|
||||
auto prevInsertBlock = mBfIRBuilder->GetInsertBlock();
|
||||
|
@ -19229,6 +19234,9 @@ void BfModule::DoMethodDeclaration(BfMethodDeclaration* methodDeclaration, bool
|
|||
if (hadParams)
|
||||
break;
|
||||
|
||||
if ((paramDef != NULL) && (paramDef->mParamKind == BfParamKind_VarArgs))
|
||||
continue;
|
||||
|
||||
if ((paramDef != NULL) && (mCompiler->mResolvePassData != NULL) && (mCompiler->mResolvePassData->mAutoComplete != NULL) &&
|
||||
(paramDef->mParamKind != BfParamKind_AppendIdx))
|
||||
mCompiler->mResolvePassData->mAutoComplete->CheckTypeRef(paramDef->mTypeRef, false);
|
||||
|
|
|
@ -6470,7 +6470,7 @@ BfType* BfModule::ResolveTypeRef(BfTypeReference* typeRef, BfPopulateType popula
|
|||
|
||||
if (auto dotType = BfNodeDynCastExact<BfDotTypeReference>(typeRef))
|
||||
{
|
||||
Fail("Invalid use of '.'", typeRef);
|
||||
Fail(StrFormat("Invalid use of '%s'", BfTokenToString(dotType->mDotToken->mToken)), typeRef);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -2067,12 +2067,22 @@ void BfParser::NextToken(int endIdx)
|
|||
break;
|
||||
case '.':
|
||||
if (mSrc[mSrcIdx] == '.')
|
||||
{
|
||||
if (mSrc[mSrcIdx + 1] == '.')
|
||||
{
|
||||
mSrcIdx += 2;
|
||||
mTokenEnd = mSrcIdx;
|
||||
mToken = BfToken_DotDotDot;
|
||||
mSyntaxToken = BfSyntaxToken_Token;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSrcIdx++;
|
||||
mTokenEnd = mSrcIdx;
|
||||
mToken = BfToken_DotDot;
|
||||
mSyntaxToken = BfSyntaxToken_Token;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mToken = BfToken_Dot;
|
||||
|
|
|
@ -4446,7 +4446,15 @@ BfTypeReference* BfReducer::DoCreateTypeRef(BfAstNode* firstNode, CreateTypeRefF
|
|||
dotTypeRef->mDotToken = tokenNode;
|
||||
firstNode = dotTypeRef;
|
||||
isHandled = true;
|
||||
//return dotTypeRef;
|
||||
}
|
||||
else if (token == BfToken_DotDotDot)
|
||||
{
|
||||
auto dotTypeRef = mAlloc->Alloc<BfDotTypeReference>();
|
||||
ReplaceNode(firstNode, dotTypeRef);
|
||||
dotTypeRef->mDotToken = tokenNode;
|
||||
firstNode = dotTypeRef;
|
||||
isHandled = true;
|
||||
return dotTypeRef;
|
||||
}
|
||||
else if ((token == BfToken_Star) && (mAllowTypeWildcard))
|
||||
{
|
||||
|
@ -8402,7 +8410,8 @@ BfTokenNode* BfReducer::ParseMethodParams(BfAstNode* node, SizedArrayImpl<BfPara
|
|||
(token == BfToken_Out) || (token == BfToken_Ref) || (token == BfToken_Mut) ||
|
||||
(token == BfToken_Delegate) || (token == BfToken_Function) ||
|
||||
(token == BfToken_Params) || (token == BfToken_LParen) ||
|
||||
(token == BfToken_Var) || (token == BfToken_LBracket)))
|
||||
(token == BfToken_Var) || (token == BfToken_LBracket) ||
|
||||
(token == BfToken_DotDotDot)))
|
||||
{
|
||||
// These get picked up below
|
||||
}
|
||||
|
@ -8453,7 +8462,8 @@ BfTokenNode* BfReducer::ParseMethodParams(BfAstNode* node, SizedArrayImpl<BfPara
|
|||
{
|
||||
BfToken token = tokenNode->GetToken();
|
||||
if ((token == BfToken_Var) || (token == BfToken_LParen) ||
|
||||
(token == BfToken_Delegate) || (token == BfToken_Function))
|
||||
(token == BfToken_Delegate) || (token == BfToken_Function) ||
|
||||
(token == BfToken_DotDotDot))
|
||||
{
|
||||
mVisitorPos.MoveNext();
|
||||
typeRef = CreateTypeRef(tokenNode);
|
||||
|
@ -8517,6 +8527,9 @@ BfTokenNode* BfReducer::ParseMethodParams(BfAstNode* node, SizedArrayImpl<BfPara
|
|||
MEMBER_SET(paramDecl, mModToken, modTokenNode);
|
||||
}
|
||||
|
||||
if ((tokenNode != NULL) && (tokenNode->mToken == BfToken_DotDotDot))
|
||||
continue;
|
||||
|
||||
bool allowNameFail = false;
|
||||
bool nextIsName = false;
|
||||
auto afterNameTokenNode = BfNodeDynCast<BfTokenNode>(mVisitorPos.Get(mVisitorPos.mReadPos + 2));
|
||||
|
|
|
@ -216,7 +216,6 @@ public:
|
|||
BfTypeReference* CreateTypeRef(BfAstNode* identifierNode, CreateTypeRefFlags createTypeRefFlags = CreateTypeRefFlags_None);
|
||||
BfTypeReference* CreateTypeRefAfter(BfAstNode* astNode, CreateTypeRefFlags createTypeRefFlags = CreateTypeRefFlags_None);
|
||||
BfTypeReference* CreateRefTypeRef(BfTypeReference* elementType, BfTokenNode* refToken);
|
||||
BfTypeReference* CreateConstTypeRef(BfTypeReference* elementType, BfTokenNode* refToken);
|
||||
bool ParseMethod(BfMethodDeclaration* methodDeclaration, SizedArrayImpl<BfParameterDeclaration*>* params, SizedArrayImpl<BfTokenNode*>* commas, bool alwaysIncludeBlock = false);
|
||||
BfGenericArgumentsNode* CreateGenericArguments(BfTokenNode* tokenNode);
|
||||
BfGenericParamsDeclaration* CreateGenericParamsDeclaration(BfTokenNode* tokenNode);
|
||||
|
|
|
@ -628,6 +628,13 @@ bool BfMethodInstance::IsSkipCall(bool bypassVirtual)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool BfMethodInstance::IsVarArgs()
|
||||
{
|
||||
if (mMethodDef->mParams.IsEmpty())
|
||||
return false;
|
||||
return mMethodDef->mParams.back()->mParamKind == BfParamKind_VarArgs;
|
||||
}
|
||||
|
||||
bool BfMethodInstance::AlwaysInline()
|
||||
{
|
||||
return mAlwaysInline;
|
||||
|
@ -856,11 +863,6 @@ int BfMethodInstance::DbgGetVirtualMethodNum()
|
|||
|
||||
void BfMethodInstance::GetIRFunctionInfo(BfModule* module, BfIRType& returnType, SizedArrayImpl<BfIRType>& paramTypes, bool forceStatic)
|
||||
{
|
||||
if ((mMethodDef->mNoSplat) && (GetOwner()->GetLoweredType() != BfTypeCode_None))
|
||||
{
|
||||
NOP;
|
||||
}
|
||||
|
||||
module->PopulateType(mReturnType);
|
||||
if (mReturnType->IsValuelessType())
|
||||
{
|
||||
|
|
|
@ -810,6 +810,7 @@ public:
|
|||
bool HasSelf();
|
||||
bool IsAutocompleteMethod() { /*return mIdHash == -1;*/ return mIsAutocompleteMethod; }
|
||||
bool IsSkipCall(bool bypassVirtual = false);
|
||||
bool IsVarArgs();
|
||||
bool AlwaysInline();
|
||||
BfImportCallKind GetImportCallKind();
|
||||
bool IsTestMethod();
|
||||
|
|
|
@ -456,6 +456,7 @@ enum BfParamKind : uint8
|
|||
BfParamKind_DelegateParam,
|
||||
BfParamKind_ImplicitCapture,
|
||||
BfParamKind_AppendIdx,
|
||||
BfParamKind_VarArgs
|
||||
};
|
||||
|
||||
class BfParameterDef
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue