зеркало из https://github.com/microsoft/clang-1.git
Refactor the way we handle operator overloading and template
instantiation for binary operators. This change moves most of the operator-overloading code from the parser action ActOnBinOp to a new, parser-independent semantic checking routine CreateOverloadedBinOp. Of particular importance is the fact that CreateOverloadedBinOp does *not* perform any name lookup based on the current parsing context (it doesn't take a Scope*), since it has to be usable during template instantiation, when there is no scope information. Rather, it takes a pre-computed set of functions that are visible from the context or via argument-dependent lookup, and adds to that set any member operators and built-in operator candidates. The set of functions is computed in the parser action ActOnBinOp based on the current context (both operator name lookup and argument-dependent lookup). Within a template, the set computed by ActOnBinOp is saved within the type-dependent AST node and is augmented with the results of argument-dependent name lookup at instantiation time (see TemplateExprInstantiator::VisitCXXOperatorCallExpr). Sadly, we can't fully test this yet. I'll follow up with template instantiation for sizeof so that the real fun can begin. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@66923 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Родитель
13135a6171
Коммит
063daf6e19
|
@ -1245,6 +1245,14 @@ public:
|
|||
/// corresponds to, e.g. "<<=".
|
||||
static const char *getOpcodeStr(Opcode Op);
|
||||
|
||||
/// \brief Retrieve the binary opcode that corresponds to the given
|
||||
/// overloaded operator.
|
||||
static Opcode getOverloadedOpcode(OverloadedOperatorKind OO);
|
||||
|
||||
/// \brief Retrieve the overloaded operator kind that corresponds to
|
||||
/// the given binary opcode.
|
||||
static OverloadedOperatorKind getOverloadedOperator(Opcode Opc);
|
||||
|
||||
/// predicates to categorize the respective opcodes.
|
||||
bool isMultiplicativeOp() const { return Opc >= Mul && Opc <= Rem; }
|
||||
bool isAdditiveOp() const { return Opc == Add || Opc == Sub; }
|
||||
|
|
|
@ -40,14 +40,19 @@ namespace clang {
|
|||
/// function templates that were found by name lookup at template
|
||||
/// definition time.
|
||||
class CXXOperatorCallExpr : public CallExpr {
|
||||
/// \brief The overloaded operator.
|
||||
OverloadedOperatorKind Operator;
|
||||
|
||||
public:
|
||||
CXXOperatorCallExpr(ASTContext& C, Expr *fn, Expr **args, unsigned numargs,
|
||||
QualType t, SourceLocation operatorloc)
|
||||
: CallExpr(C, CXXOperatorCallExprClass, fn, args, numargs, t, operatorloc){}
|
||||
CXXOperatorCallExpr(ASTContext& C, OverloadedOperatorKind Op, Expr *fn,
|
||||
Expr **args, unsigned numargs, QualType t,
|
||||
SourceLocation operatorloc)
|
||||
: CallExpr(C, CXXOperatorCallExprClass, fn, args, numargs, t, operatorloc),
|
||||
Operator(Op) {}
|
||||
|
||||
/// getOperator - Returns the kind of overloaded operator that this
|
||||
/// expression refers to.
|
||||
OverloadedOperatorKind getOperator() const;
|
||||
OverloadedOperatorKind getOperator() const { return Operator; }
|
||||
|
||||
/// getOperatorLoc - Returns the location of the operator symbol in
|
||||
/// the expression. When @c getOperator()==OO_Call, this is the
|
||||
|
|
|
@ -361,7 +361,7 @@ public:
|
|||
/// that its definition somehow depends on a template parameter
|
||||
/// (C++ [temp.dep.type]).
|
||||
bool isDependentType() const { return Dependent; }
|
||||
bool isOverloadType() const; // C++ overloaded function
|
||||
bool isOverloadableType() const;
|
||||
|
||||
/// hasPointerRepresentation - Whether this type is represented
|
||||
/// natively as a pointer; this includes pointers, references, block
|
||||
|
@ -1860,8 +1860,10 @@ inline bool Type::isSpecificBuiltinType(unsigned K) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
inline bool Type::isOverloadType() const {
|
||||
return isSpecificBuiltinType(BuiltinType::Overload);
|
||||
/// \brief Determines whether this is a type for which one can define
|
||||
/// an overloaded operator.
|
||||
inline bool Type::isOverloadableType() const {
|
||||
return isDependentType() || isRecordType() || isEnumeralType();
|
||||
}
|
||||
|
||||
inline bool Type::hasPointerRepresentation() const {
|
||||
|
|
|
@ -227,6 +227,68 @@ const char *BinaryOperator::getOpcodeStr(Opcode Op) {
|
|||
return "";
|
||||
}
|
||||
|
||||
BinaryOperator::Opcode
|
||||
BinaryOperator::getOverloadedOpcode(OverloadedOperatorKind OO) {
|
||||
switch (OO) {
|
||||
case OO_Plus: return Add;
|
||||
case OO_Minus: return Sub;
|
||||
case OO_Star: return Mul;
|
||||
case OO_Slash: return Div;
|
||||
case OO_Percent: return Rem;
|
||||
case OO_Caret: return Xor;
|
||||
case OO_Amp: return And;
|
||||
case OO_Pipe: return Or;
|
||||
case OO_Equal: return Assign;
|
||||
case OO_Less: return LT;
|
||||
case OO_Greater: return GT;
|
||||
case OO_PlusEqual: return AddAssign;
|
||||
case OO_MinusEqual: return SubAssign;
|
||||
case OO_StarEqual: return MulAssign;
|
||||
case OO_SlashEqual: return DivAssign;
|
||||
case OO_PercentEqual: return RemAssign;
|
||||
case OO_CaretEqual: return XorAssign;
|
||||
case OO_AmpEqual: return AndAssign;
|
||||
case OO_PipeEqual: return OrAssign;
|
||||
case OO_LessLess: return Shl;
|
||||
case OO_GreaterGreater: return Shr;
|
||||
case OO_LessLessEqual: return ShlAssign;
|
||||
case OO_GreaterGreaterEqual: return ShrAssign;
|
||||
case OO_EqualEqual: return EQ;
|
||||
case OO_ExclaimEqual: return NE;
|
||||
case OO_LessEqual: return LE;
|
||||
case OO_GreaterEqual: return GE;
|
||||
case OO_AmpAmp: return LAnd;
|
||||
case OO_PipePipe: return LOr;
|
||||
case OO_Comma: return Comma;
|
||||
case OO_ArrowStar: return PtrMemI;
|
||||
default: assert(false && "Not an overloadable binary operator");
|
||||
}
|
||||
}
|
||||
|
||||
OverloadedOperatorKind BinaryOperator::getOverloadedOperator(Opcode Opc) {
|
||||
static const OverloadedOperatorKind OverOps[] = {
|
||||
/* .* Cannot be overloaded */OO_None, OO_ArrowStar,
|
||||
OO_Star, OO_Slash, OO_Percent,
|
||||
OO_Plus, OO_Minus,
|
||||
OO_LessLess, OO_GreaterGreater,
|
||||
OO_Less, OO_Greater, OO_LessEqual, OO_GreaterEqual,
|
||||
OO_EqualEqual, OO_ExclaimEqual,
|
||||
OO_Amp,
|
||||
OO_Caret,
|
||||
OO_Pipe,
|
||||
OO_AmpAmp,
|
||||
OO_PipePipe,
|
||||
OO_Equal, OO_StarEqual,
|
||||
OO_SlashEqual, OO_PercentEqual,
|
||||
OO_PlusEqual, OO_MinusEqual,
|
||||
OO_LessLessEqual, OO_GreaterGreaterEqual,
|
||||
OO_AmpEqual, OO_CaretEqual,
|
||||
OO_PipeEqual,
|
||||
OO_Comma
|
||||
};
|
||||
return OverOps[Opc];
|
||||
}
|
||||
|
||||
InitListExpr::InitListExpr(SourceLocation lbraceloc,
|
||||
Expr **initExprs, unsigned numInits,
|
||||
SourceLocation rbraceloc)
|
||||
|
|
|
@ -160,27 +160,6 @@ bool UnaryTypeTraitExpr::EvaluateTrait() const {
|
|||
}
|
||||
}
|
||||
|
||||
OverloadedOperatorKind CXXOperatorCallExpr::getOperator() const {
|
||||
// All simple function calls (e.g. func()) are implicitly cast to pointer to
|
||||
// function. As a result, we try and obtain the DeclRefExpr from the
|
||||
// ImplicitCastExpr.
|
||||
const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(getCallee());
|
||||
if (!ICE) // FIXME: deal with more complex calls (e.g. (func)(), (*func)()).
|
||||
return OO_None;
|
||||
|
||||
const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(ICE->getSubExpr());
|
||||
if (!DRE)
|
||||
return OO_None;
|
||||
|
||||
if (const FunctionDecl *FDecl = dyn_cast<FunctionDecl>(DRE->getDecl()))
|
||||
return FDecl->getDeclName().getCXXOverloadedOperator();
|
||||
else if (const OverloadedFunctionDecl *Ovl
|
||||
= dyn_cast<OverloadedFunctionDecl>(DRE->getDecl()))
|
||||
return Ovl->getDeclName().getCXXOverloadedOperator();
|
||||
else
|
||||
return OO_None;
|
||||
}
|
||||
|
||||
SourceRange CXXOperatorCallExpr::getSourceRange() const {
|
||||
OverloadedOperatorKind Kind = getOperator();
|
||||
if (Kind == OO_PlusPlus || Kind == OO_MinusMinus) {
|
||||
|
|
|
@ -523,10 +523,18 @@ public:
|
|||
OR_Deleted ///< Overload resoltuion refers to a deleted function.
|
||||
};
|
||||
|
||||
typedef llvm::SmallPtrSet<FunctionDecl *, 16> FunctionSet;
|
||||
typedef llvm::SmallPtrSet<NamespaceDecl *, 16> AssociatedNamespaceSet;
|
||||
typedef llvm::SmallPtrSet<CXXRecordDecl *, 16> AssociatedClassSet;
|
||||
|
||||
void AddOverloadCandidate(FunctionDecl *Function,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
bool SuppressUserConversions = false);
|
||||
void AddFunctionCandidates(const FunctionSet &Functions,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
bool SuppressUserConversions = false);
|
||||
void AddMethodCandidate(CXXMethodDecl *Method,
|
||||
Expr *Object, Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
|
@ -538,11 +546,16 @@ public:
|
|||
const FunctionProtoType *Proto,
|
||||
Expr *Object, Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet);
|
||||
bool AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
||||
void AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
||||
SourceLocation OpLoc,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
SourceRange OpRange = SourceRange());
|
||||
void AddMemberOperatorCandidates(OverloadedOperatorKind Op,
|
||||
SourceLocation OpLoc,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
SourceRange OpRange = SourceRange());
|
||||
void AddBuiltinCandidate(QualType ResultTy, QualType *ParamTys,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
|
@ -572,6 +585,12 @@ public:
|
|||
SourceLocation *CommaLocs,
|
||||
SourceLocation RParenLoc,
|
||||
bool &ArgumentDependentLookup);
|
||||
|
||||
OwningExprResult CreateOverloadedBinOp(SourceLocation OpLoc,
|
||||
unsigned Opc,
|
||||
FunctionSet &Functions,
|
||||
Expr *LHS, Expr *RHS);
|
||||
|
||||
ExprResult
|
||||
BuildCallToMemberFunction(Scope *S, Expr *MemExpr,
|
||||
SourceLocation LParenLoc, Expr **Args,
|
||||
|
@ -930,10 +949,6 @@ public:
|
|||
bool AllowBuiltinCreation = true,
|
||||
SourceLocation Loc = SourceLocation());
|
||||
|
||||
typedef llvm::SmallPtrSet<FunctionDecl *, 16> FunctionSet;
|
||||
typedef llvm::SmallPtrSet<NamespaceDecl *, 16> AssociatedNamespaceSet;
|
||||
typedef llvm::SmallPtrSet<CXXRecordDecl *, 16> AssociatedClassSet;
|
||||
|
||||
void LookupOverloadedOperatorName(OverloadedOperatorKind Op, Scope *S,
|
||||
QualType T1, QualType T2,
|
||||
FunctionSet &Functions);
|
||||
|
|
|
@ -1694,7 +1694,7 @@ Sema::CheckReferenceInit(Expr *&Init, QualType &DeclType,
|
|||
// If the initializer is the address of an overloaded function, try
|
||||
// to resolve the overloaded function. If all goes well, T2 is the
|
||||
// type of the resulting function.
|
||||
if (T2->isOverloadType()) {
|
||||
if (Context.getCanonicalType(T2) == Context.OverloadTy) {
|
||||
FunctionDecl *Fn = ResolveAddressOfOverloadedFunction(Init, DeclType,
|
||||
ICS != 0);
|
||||
if (Fn) {
|
||||
|
|
|
@ -1324,8 +1324,7 @@ Sema::ActOnPostfixUnaryOp(Scope *S, SourceLocation OpLoc,
|
|||
|
||||
// Build the candidate set for overloading
|
||||
OverloadCandidateSet CandidateSet;
|
||||
if (AddOperatorCandidates(OverOp, S, OpLoc, Args, 2, CandidateSet))
|
||||
return ExprError();
|
||||
AddOperatorCandidates(OverOp, S, OpLoc, Args, 2, CandidateSet);
|
||||
|
||||
// Perform overload resolution.
|
||||
OverloadCandidateSet::iterator Best;
|
||||
|
@ -1361,8 +1360,9 @@ Sema::ActOnPostfixUnaryOp(Scope *S, SourceLocation OpLoc,
|
|||
UsualUnaryConversions(FnExpr);
|
||||
|
||||
Input.release();
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, FnExpr, Args, 2,
|
||||
ResultTy, OpLoc));
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, OverOp, FnExpr,
|
||||
Args, 2, ResultTy,
|
||||
OpLoc));
|
||||
} else {
|
||||
// We matched a built-in operator. Convert the arguments, then
|
||||
// break out so that we will build the appropriate built-in
|
||||
|
@ -1424,9 +1424,8 @@ Sema::ActOnArraySubscriptExpr(Scope *S, ExprArg Base, SourceLocation LLoc,
|
|||
// to the candidate set.
|
||||
OverloadCandidateSet CandidateSet;
|
||||
Expr *Args[2] = { LHSExp, RHSExp };
|
||||
if (AddOperatorCandidates(OO_Subscript, S, LLoc, Args, 2, CandidateSet,
|
||||
SourceRange(LLoc, RLoc)))
|
||||
return ExprError();
|
||||
AddOperatorCandidates(OO_Subscript, S, LLoc, Args, 2, CandidateSet,
|
||||
SourceRange(LLoc, RLoc));
|
||||
|
||||
// Perform overload resolution.
|
||||
OverloadCandidateSet::iterator Best;
|
||||
|
@ -1469,7 +1468,8 @@ Sema::ActOnArraySubscriptExpr(Scope *S, ExprArg Base, SourceLocation LLoc,
|
|||
|
||||
Base.release();
|
||||
Idx.release();
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, FnExpr, Args, 2,
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, OO_Subscript,
|
||||
FnExpr, Args, 2,
|
||||
ResultTy, LLoc));
|
||||
} else {
|
||||
// We matched a built-in operator. Convert the arguments, then
|
||||
|
@ -3977,32 +3977,6 @@ Action::OwningExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc,
|
|||
CompTy, OpLoc));
|
||||
}
|
||||
|
||||
static OverloadedOperatorKind
|
||||
getOverloadedOperator(BinaryOperator::Opcode Opc) {
|
||||
static const OverloadedOperatorKind OverOps[] = {
|
||||
// Overloading .* is not possible.
|
||||
static_cast<OverloadedOperatorKind>(0), OO_ArrowStar,
|
||||
OO_Star, OO_Slash, OO_Percent,
|
||||
OO_Plus, OO_Minus,
|
||||
OO_LessLess, OO_GreaterGreater,
|
||||
OO_Less, OO_Greater, OO_LessEqual, OO_GreaterEqual,
|
||||
OO_EqualEqual, OO_ExclaimEqual,
|
||||
OO_Amp,
|
||||
OO_Caret,
|
||||
OO_Pipe,
|
||||
OO_AmpAmp,
|
||||
OO_PipePipe,
|
||||
OO_Equal, OO_StarEqual,
|
||||
OO_SlashEqual, OO_PercentEqual,
|
||||
OO_PlusEqual, OO_MinusEqual,
|
||||
OO_LessLessEqual, OO_GreaterGreaterEqual,
|
||||
OO_AmpEqual, OO_CaretEqual,
|
||||
OO_PipeEqual,
|
||||
OO_Comma
|
||||
};
|
||||
return OverOps[Opc];
|
||||
}
|
||||
|
||||
// Binary Operators. 'Tok' is the token for the operator.
|
||||
Action::OwningExprResult Sema::ActOnBinOp(Scope *S, SourceLocation TokLoc,
|
||||
tok::TokenKind Kind,
|
||||
|
@ -4013,139 +3987,27 @@ Action::OwningExprResult Sema::ActOnBinOp(Scope *S, SourceLocation TokLoc,
|
|||
assert((lhs != 0) && "ActOnBinOp(): missing left expression");
|
||||
assert((rhs != 0) && "ActOnBinOp(): missing right expression");
|
||||
|
||||
// If either expression is type-dependent, just build the AST.
|
||||
if (lhs->isTypeDependent() || rhs->isTypeDependent()) {
|
||||
// .* cannot be overloaded.
|
||||
if (Opc == BinaryOperator::PtrMemD)
|
||||
return Owned(new (Context) BinaryOperator(lhs, rhs, Opc,
|
||||
Context.DependentTy, TokLoc));
|
||||
|
||||
// Find all of the overloaded operators visible from the template
|
||||
// definition. We perform both an operator-name lookup from the
|
||||
// local scope and an argument-dependent lookup based on the types
|
||||
// of the arguments.
|
||||
if (getLangOptions().CPlusPlus &&
|
||||
(lhs->getType()->isOverloadableType() ||
|
||||
rhs->getType()->isOverloadableType())) {
|
||||
// Find all of the overloaded operators visible from this
|
||||
// point. We perform both an operator-name lookup from the local
|
||||
// scope and an argument-dependent lookup based on the types of
|
||||
// the arguments.
|
||||
FunctionSet Functions;
|
||||
OverloadedOperatorKind OverOp = getOverloadedOperator(Opc);
|
||||
LookupOverloadedOperatorName(OverOp, S, lhs->getType(), rhs->getType(),
|
||||
Functions);
|
||||
Expr *Args[2] = { lhs, rhs };
|
||||
DeclarationName OpName
|
||||
= Context.DeclarationNames.getCXXOperatorName(OverOp);
|
||||
ArgumentDependentLookup(OpName, Args, 2, Functions);
|
||||
|
||||
OverloadedFunctionDecl *Overloads
|
||||
= OverloadedFunctionDecl::Create(Context, CurContext, OpName);
|
||||
for (FunctionSet::iterator Func = Functions.begin(),
|
||||
FuncEnd = Functions.end();
|
||||
Func != FuncEnd; ++Func)
|
||||
Overloads->addOverload(*Func);
|
||||
|
||||
DeclRefExpr *Fn = new (Context) DeclRefExpr(Overloads, Context.OverloadTy,
|
||||
TokLoc, false, false);
|
||||
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, Fn,
|
||||
Args, 2,
|
||||
Context.DependentTy,
|
||||
TokLoc));
|
||||
}
|
||||
|
||||
if (getLangOptions().CPlusPlus && Opc != BinaryOperator::PtrMemD &&
|
||||
(lhs->getType()->isRecordType() || lhs->getType()->isEnumeralType() ||
|
||||
rhs->getType()->isRecordType() || rhs->getType()->isEnumeralType())) {
|
||||
// If this is one of the assignment operators, we only perform
|
||||
// overload resolution if the left-hand side is a class or
|
||||
// enumeration type (C++ [expr.ass]p3).
|
||||
if (Opc >= BinaryOperator::Assign && Opc <= BinaryOperator::OrAssign &&
|
||||
!(lhs->getType()->isRecordType() || lhs->getType()->isEnumeralType())) {
|
||||
return CreateBuiltinBinOp(TokLoc, Opc, lhs, rhs);
|
||||
OverloadedOperatorKind OverOp = BinaryOperator::getOverloadedOperator(Opc);
|
||||
if (OverOp != OO_None) {
|
||||
LookupOverloadedOperatorName(OverOp, S, lhs->getType(), rhs->getType(),
|
||||
Functions);
|
||||
Expr *Args[2] = { lhs, rhs };
|
||||
DeclarationName OpName
|
||||
= Context.DeclarationNames.getCXXOperatorName(OverOp);
|
||||
ArgumentDependentLookup(OpName, Args, 2, Functions);
|
||||
}
|
||||
|
||||
// Determine which overloaded operator we're dealing with.
|
||||
|
||||
// Add the appropriate overloaded operators (C++ [over.match.oper])
|
||||
// to the candidate set.
|
||||
OverloadCandidateSet CandidateSet;
|
||||
OverloadedOperatorKind OverOp = getOverloadedOperator(Opc);
|
||||
Expr *Args[2] = { lhs, rhs };
|
||||
if (AddOperatorCandidates(OverOp, S, TokLoc, Args, 2, CandidateSet))
|
||||
return ExprError();
|
||||
|
||||
// Perform overload resolution.
|
||||
OverloadCandidateSet::iterator Best;
|
||||
switch (BestViableFunction(CandidateSet, Best)) {
|
||||
case OR_Success: {
|
||||
// We found a built-in operator or an overloaded operator.
|
||||
FunctionDecl *FnDecl = Best->Function;
|
||||
|
||||
if (FnDecl) {
|
||||
// We matched an overloaded operator. Build a call to that
|
||||
// operator.
|
||||
|
||||
// Convert the arguments.
|
||||
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
|
||||
if (PerformObjectArgumentInitialization(lhs, Method) ||
|
||||
PerformCopyInitialization(rhs, FnDecl->getParamDecl(0)->getType(),
|
||||
"passing"))
|
||||
return ExprError();
|
||||
} else {
|
||||
// Convert the arguments.
|
||||
if (PerformCopyInitialization(lhs, FnDecl->getParamDecl(0)->getType(),
|
||||
"passing") ||
|
||||
PerformCopyInitialization(rhs, FnDecl->getParamDecl(1)->getType(),
|
||||
"passing"))
|
||||
return ExprError();
|
||||
}
|
||||
|
||||
// Determine the result type
|
||||
QualType ResultTy
|
||||
= FnDecl->getType()->getAsFunctionType()->getResultType();
|
||||
ResultTy = ResultTy.getNonReferenceType();
|
||||
|
||||
// Build the actual expression node.
|
||||
Expr *FnExpr = new (Context) DeclRefExpr(FnDecl, FnDecl->getType(),
|
||||
SourceLocation());
|
||||
UsualUnaryConversions(FnExpr);
|
||||
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, FnExpr, Args, 2,
|
||||
ResultTy, TokLoc));
|
||||
} else {
|
||||
// We matched a built-in operator. Convert the arguments, then
|
||||
// break out so that we will build the appropriate built-in
|
||||
// operator node.
|
||||
if (PerformImplicitConversion(lhs, Best->BuiltinTypes.ParamTypes[0],
|
||||
Best->Conversions[0], "passing") ||
|
||||
PerformImplicitConversion(rhs, Best->BuiltinTypes.ParamTypes[1],
|
||||
Best->Conversions[1], "passing"))
|
||||
return ExprError();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case OR_No_Viable_Function:
|
||||
// No viable function; fall through to handling this as a
|
||||
// built-in operator, which will produce an error message for us.
|
||||
break;
|
||||
|
||||
case OR_Ambiguous:
|
||||
Diag(TokLoc, diag::err_ovl_ambiguous_oper)
|
||||
<< BinaryOperator::getOpcodeStr(Opc)
|
||||
<< lhs->getSourceRange() << rhs->getSourceRange();
|
||||
PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
|
||||
return ExprError();
|
||||
|
||||
case OR_Deleted:
|
||||
Diag(TokLoc, diag::err_ovl_deleted_oper)
|
||||
<< Best->Function->isDeleted()
|
||||
<< BinaryOperator::getOpcodeStr(Opc)
|
||||
<< lhs->getSourceRange() << rhs->getSourceRange();
|
||||
PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
|
||||
return ExprError();
|
||||
}
|
||||
|
||||
// Either we found no viable overloaded operator or we matched a
|
||||
// built-in operator. In either case, fall through to trying to
|
||||
// build a built-in operation.
|
||||
// Build the (potentially-overloaded, potentially-dependent)
|
||||
// binary operation.
|
||||
return CreateOverloadedBinOp(TokLoc, Opc, Functions, lhs, rhs);
|
||||
}
|
||||
|
||||
// Build a built-in binary operation.
|
||||
|
@ -4178,9 +4040,8 @@ Action::OwningExprResult Sema::ActOnUnaryOp(Scope *S, SourceLocation OpLoc,
|
|||
// Add the appropriate overloaded operators (C++ [over.match.oper])
|
||||
// to the candidate set.
|
||||
OverloadCandidateSet CandidateSet;
|
||||
if (OverOp != OO_None &&
|
||||
AddOperatorCandidates(OverOp, S, OpLoc, &Input, 1, CandidateSet))
|
||||
return ExprError();
|
||||
if (OverOp != OO_None)
|
||||
AddOperatorCandidates(OverOp, S, OpLoc, &Input, 1, CandidateSet);
|
||||
|
||||
// Perform overload resolution.
|
||||
OverloadCandidateSet::iterator Best;
|
||||
|
@ -4216,8 +4077,9 @@ Action::OwningExprResult Sema::ActOnUnaryOp(Scope *S, SourceLocation OpLoc,
|
|||
UsualUnaryConversions(FnExpr);
|
||||
|
||||
input.release();
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, FnExpr, &Input,
|
||||
1, ResultTy, OpLoc));
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, OverOp, FnExpr,
|
||||
&Input, 1, ResultTy,
|
||||
OpLoc));
|
||||
} else {
|
||||
// We matched a built-in operator. Convert the arguments, then
|
||||
// break out so that we will build the appropriate built-in
|
||||
|
|
|
@ -800,7 +800,7 @@ Sema::PerformImplicitConversion(Expr *&From, QualType ToType,
|
|||
break;
|
||||
|
||||
case ICK_Function_To_Pointer:
|
||||
if (FromType->isOverloadType()) {
|
||||
if (Context.getCanonicalType(FromType) == Context.OverloadTy) {
|
||||
FunctionDecl *Fn = ResolveAddressOfOverloadedFunction(From, ToType, true);
|
||||
if (!Fn)
|
||||
return true;
|
||||
|
|
|
@ -468,7 +468,7 @@ Sema::IsStandardConversion(Expr* From, QualType ToType,
|
|||
Expr::isLvalueResult argIsLvalue = From->isLvalue(Context);
|
||||
if (argIsLvalue == Expr::LV_Valid &&
|
||||
!FromType->isFunctionType() && !FromType->isArrayType() &&
|
||||
!FromType->isOverloadType()) {
|
||||
Context.getCanonicalType(FromType) != Context.OverloadTy) {
|
||||
SCS.First = ICK_Lvalue_To_Rvalue;
|
||||
|
||||
// If T is a non-class type, the type of the rvalue is the
|
||||
|
@ -2064,6 +2064,19 @@ Sema::AddOverloadCandidate(FunctionDecl *Function,
|
|||
}
|
||||
}
|
||||
|
||||
/// \brief Add all of the function declarations in the given function set to
|
||||
/// the overload canddiate set.
|
||||
void Sema::AddFunctionCandidates(const FunctionSet &Functions,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
bool SuppressUserConversions) {
|
||||
for (FunctionSet::const_iterator F = Functions.begin(),
|
||||
FEnd = Functions.end();
|
||||
F != FEnd; ++F)
|
||||
AddOverloadCandidate(*F, Args, NumArgs, CandidateSet,
|
||||
SuppressUserConversions);
|
||||
}
|
||||
|
||||
/// AddMethodCandidate - Adds the given C++ member function to the set
|
||||
/// of candidate functions, using the given function call arguments
|
||||
/// and the object argument (@c Object). For example, in a call
|
||||
|
@ -2308,17 +2321,43 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
|
|||
}
|
||||
}
|
||||
|
||||
/// AddOperatorCandidates - Add the overloaded operator candidates for
|
||||
/// the operator Op that was used in an operator expression such as "x
|
||||
/// Op y". S is the scope in which the expression occurred (used for
|
||||
/// name lookup of the operator), Args/NumArgs provides the operator
|
||||
/// arguments, and CandidateSet will store the added overload
|
||||
/// candidates. (C++ [over.match.oper]).
|
||||
bool Sema::AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
||||
// FIXME: This will eventually be removed, once we've migrated all of
|
||||
// the operator overloading logic over to the scheme used by binary
|
||||
// operators, which works for template instantiation.
|
||||
void Sema::AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
||||
SourceLocation OpLoc,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
SourceRange OpRange) {
|
||||
|
||||
FunctionSet Functions;
|
||||
|
||||
QualType T1 = Args[0]->getType();
|
||||
QualType T2;
|
||||
if (NumArgs > 1)
|
||||
T2 = Args[1]->getType();
|
||||
|
||||
DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
|
||||
LookupOverloadedOperatorName(Op, S, T1, T2, Functions);
|
||||
ArgumentDependentLookup(OpName, Args, NumArgs, Functions);
|
||||
AddFunctionCandidates(Functions, Args, NumArgs, CandidateSet);
|
||||
AddMemberOperatorCandidates(Op, OpLoc, Args, NumArgs, CandidateSet, OpRange);
|
||||
AddBuiltinOperatorCandidates(Op, Args, NumArgs, CandidateSet);
|
||||
}
|
||||
|
||||
/// \brief Add overload candidates for overloaded operators that are
|
||||
/// member functions.
|
||||
///
|
||||
/// Add the overloaded operator candidates that are member functions
|
||||
/// for the operator Op that was used in an operator expression such
|
||||
/// as "x Op y". , Args/NumArgs provides the operator arguments, and
|
||||
/// CandidateSet will store the added overload candidates. (C++
|
||||
/// [over.match.oper]).
|
||||
void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op,
|
||||
SourceLocation OpLoc,
|
||||
Expr **Args, unsigned NumArgs,
|
||||
OverloadCandidateSet& CandidateSet,
|
||||
SourceRange OpRange) {
|
||||
DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
|
||||
|
||||
// C++ [over.match.oper]p3:
|
||||
|
@ -2338,6 +2377,7 @@ bool Sema::AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
|||
// result of the qualified lookup of T1::operator@
|
||||
// (13.3.1.1.1); otherwise, the set of member candidates is
|
||||
// empty.
|
||||
// FIXME: Lookup in base classes, too!
|
||||
if (const RecordType *T1Rec = T1->getAsRecordType()) {
|
||||
DeclContext::lookup_const_iterator Oper, OperEnd;
|
||||
for (llvm::tie(Oper, OperEnd) = T1Rec->getDecl()->lookup(OpName);
|
||||
|
@ -2346,38 +2386,6 @@ bool Sema::AddOperatorCandidates(OverloadedOperatorKind Op, Scope *S,
|
|||
Args+1, NumArgs - 1, CandidateSet,
|
||||
/*SuppressUserConversions=*/false);
|
||||
}
|
||||
|
||||
FunctionSet Functions;
|
||||
|
||||
// -- The set of non-member candidates is the result of the
|
||||
// unqualified lookup of operator@ in the context of the
|
||||
// expression according to the usual rules for name lookup in
|
||||
// unqualified function calls (3.4.2) except that all member
|
||||
// functions are ignored. However, if no operand has a class
|
||||
// type, only those non-member functions in the lookup set
|
||||
// that have a first parameter of type T1 or “reference to
|
||||
// (possibly cv-qualified) T1”, when T1 is an enumeration
|
||||
// type, or (if there is a right operand) a second parameter
|
||||
// of type T2 or “reference to (possibly cv-qualified) T2”,
|
||||
// when T2 is an enumeration type, are candidate functions.
|
||||
LookupOverloadedOperatorName(Op, S, T1, T2, Functions);
|
||||
|
||||
// Since the set of non-member candidates corresponds to
|
||||
// *unqualified* lookup of the operator name, we also perform
|
||||
// argument-dependent lookup (C++ [basic.lookup.argdep]).
|
||||
ArgumentDependentLookup(OpName, Args, NumArgs, Functions);
|
||||
|
||||
// Add all of the functions found via operator name lookup and
|
||||
// argument-dependent lookup to the candidate set.
|
||||
for (FunctionSet::iterator Func = Functions.begin(),
|
||||
FuncEnd = Functions.end();
|
||||
Func != FuncEnd; ++Func)
|
||||
AddOverloadCandidate(*Func, Args, NumArgs, CandidateSet);
|
||||
|
||||
// Add builtin overload candidates (C++ [over.built]).
|
||||
AddBuiltinOperatorCandidates(Op, Args, NumArgs, CandidateSet);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// AddBuiltinCandidate - Add a candidate for a built-in
|
||||
|
@ -3615,6 +3623,161 @@ FunctionDecl *Sema::ResolveOverloadedCallFn(Expr *Fn, NamedDecl *Callee,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// \brief Create a binary operation that may resolve to an overloaded
|
||||
/// operator.
|
||||
///
|
||||
/// \param OpLoc The location of the operator itself (e.g., '+').
|
||||
///
|
||||
/// \param OpcIn The BinaryOperator::Opcode that describes this
|
||||
/// operator.
|
||||
///
|
||||
/// \param Functions The set of non-member functions that will be
|
||||
/// considered by overload resolution. The caller needs to build this
|
||||
/// set based on the context using, e.g.,
|
||||
/// LookupOverloadedOperatorName() and ArgumentDependentLookup(). This
|
||||
/// set should not contain any member functions; those will be added
|
||||
/// by CreateOverloadedBinOp().
|
||||
///
|
||||
/// \param LHS Left-hand argument.
|
||||
/// \param RHS Right-hand argument.
|
||||
Sema::OwningExprResult
|
||||
Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
|
||||
unsigned OpcIn,
|
||||
FunctionSet &Functions,
|
||||
Expr *LHS, Expr *RHS) {
|
||||
OverloadCandidateSet CandidateSet;
|
||||
Expr *Args[2] = { LHS, RHS };
|
||||
|
||||
BinaryOperator::Opcode Opc = static_cast<BinaryOperator::Opcode>(OpcIn);
|
||||
OverloadedOperatorKind Op = BinaryOperator::getOverloadedOperator(Opc);
|
||||
DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
|
||||
|
||||
// If either side is type-dependent, create an appropriate dependent
|
||||
// expression.
|
||||
if (LHS->isTypeDependent() || RHS->isTypeDependent()) {
|
||||
// .* cannot be overloaded.
|
||||
if (Opc == BinaryOperator::PtrMemD)
|
||||
return Owned(new (Context) BinaryOperator(LHS, RHS, Opc,
|
||||
Context.DependentTy, OpLoc));
|
||||
|
||||
OverloadedFunctionDecl *Overloads
|
||||
= OverloadedFunctionDecl::Create(Context, CurContext, OpName);
|
||||
for (FunctionSet::iterator Func = Functions.begin(),
|
||||
FuncEnd = Functions.end();
|
||||
Func != FuncEnd; ++Func)
|
||||
Overloads->addOverload(*Func);
|
||||
|
||||
DeclRefExpr *Fn = new (Context) DeclRefExpr(Overloads, Context.OverloadTy,
|
||||
OpLoc, false, false);
|
||||
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, Op, Fn,
|
||||
Args, 2,
|
||||
Context.DependentTy,
|
||||
OpLoc));
|
||||
}
|
||||
|
||||
// If this is the .* operator, which is not overloadable, just
|
||||
// create a built-in binary operator.
|
||||
if (Opc == BinaryOperator::PtrMemD)
|
||||
return CreateBuiltinBinOp(OpLoc, Opc, LHS, RHS);
|
||||
|
||||
// If this is one of the assignment operators, we only perform
|
||||
// overload resolution if the left-hand side is a class or
|
||||
// enumeration type (C++ [expr.ass]p3).
|
||||
if (Opc >= BinaryOperator::Assign && Opc <= BinaryOperator::OrAssign &&
|
||||
!LHS->getType()->isOverloadableType())
|
||||
return CreateBuiltinBinOp(OpLoc, Opc, LHS, RHS);
|
||||
|
||||
|
||||
// Add the candidates from the given function set.
|
||||
AddFunctionCandidates(Functions, Args, 2, CandidateSet, false);
|
||||
|
||||
// Add operator candidates that are member functions.
|
||||
AddMemberOperatorCandidates(Op, OpLoc, Args, 2, CandidateSet);
|
||||
|
||||
// Add builtin operator candidates.
|
||||
AddBuiltinOperatorCandidates(Op, Args, 2, CandidateSet);
|
||||
|
||||
// Perform overload resolution.
|
||||
OverloadCandidateSet::iterator Best;
|
||||
switch (BestViableFunction(CandidateSet, Best)) {
|
||||
case OR_Success: {
|
||||
// We found a built-in operator or an overloaded operator.
|
||||
FunctionDecl *FnDecl = Best->Function;
|
||||
|
||||
if (FnDecl) {
|
||||
// We matched an overloaded operator. Build a call to that
|
||||
// operator.
|
||||
|
||||
// Convert the arguments.
|
||||
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
|
||||
if (PerformObjectArgumentInitialization(LHS, Method) ||
|
||||
PerformCopyInitialization(RHS, FnDecl->getParamDecl(0)->getType(),
|
||||
"passing"))
|
||||
return ExprError();
|
||||
} else {
|
||||
// Convert the arguments.
|
||||
if (PerformCopyInitialization(LHS, FnDecl->getParamDecl(0)->getType(),
|
||||
"passing") ||
|
||||
PerformCopyInitialization(RHS, FnDecl->getParamDecl(1)->getType(),
|
||||
"passing"))
|
||||
return ExprError();
|
||||
}
|
||||
|
||||
// Determine the result type
|
||||
QualType ResultTy
|
||||
= FnDecl->getType()->getAsFunctionType()->getResultType();
|
||||
ResultTy = ResultTy.getNonReferenceType();
|
||||
|
||||
// Build the actual expression node.
|
||||
Expr *FnExpr = new (Context) DeclRefExpr(FnDecl, FnDecl->getType(),
|
||||
SourceLocation());
|
||||
UsualUnaryConversions(FnExpr);
|
||||
|
||||
return Owned(new (Context) CXXOperatorCallExpr(Context, Op, FnExpr,
|
||||
Args, 2, ResultTy,
|
||||
OpLoc));
|
||||
} else {
|
||||
// We matched a built-in operator. Convert the arguments, then
|
||||
// break out so that we will build the appropriate built-in
|
||||
// operator node.
|
||||
if (PerformImplicitConversion(LHS, Best->BuiltinTypes.ParamTypes[0],
|
||||
Best->Conversions[0], "passing") ||
|
||||
PerformImplicitConversion(RHS, Best->BuiltinTypes.ParamTypes[1],
|
||||
Best->Conversions[1], "passing"))
|
||||
return ExprError();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case OR_No_Viable_Function:
|
||||
// No viable function; fall through to handling this as a
|
||||
// built-in operator, which will produce an error message for us.
|
||||
break;
|
||||
|
||||
case OR_Ambiguous:
|
||||
Diag(OpLoc, diag::err_ovl_ambiguous_oper)
|
||||
<< BinaryOperator::getOpcodeStr(Opc)
|
||||
<< LHS->getSourceRange() << RHS->getSourceRange();
|
||||
PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
|
||||
return ExprError();
|
||||
|
||||
case OR_Deleted:
|
||||
Diag(OpLoc, diag::err_ovl_deleted_oper)
|
||||
<< Best->Function->isDeleted()
|
||||
<< BinaryOperator::getOpcodeStr(Opc)
|
||||
<< LHS->getSourceRange() << RHS->getSourceRange();
|
||||
PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
|
||||
return ExprError();
|
||||
}
|
||||
|
||||
// Either we found no viable overloaded operator or we matched a
|
||||
// built-in operator. In either case, try to build a built-in
|
||||
// operation.
|
||||
return CreateBuiltinBinOp(OpLoc, Opc, LHS, RHS);
|
||||
}
|
||||
|
||||
/// BuildCallToMemberFunction - Build a call to a member
|
||||
/// function. MemExpr is the expression that refers to the member
|
||||
/// function (and includes the object parameter), Args/NumArgs are the
|
||||
|
@ -3870,8 +4033,8 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Object,
|
|||
// owned.
|
||||
QualType ResultTy = Method->getResultType().getNonReferenceType();
|
||||
ExprOwningPtr<CXXOperatorCallExpr>
|
||||
TheCall(this, new (Context) CXXOperatorCallExpr(Context, NewFn, MethodArgs,
|
||||
NumArgs + 1,
|
||||
TheCall(this, new (Context) CXXOperatorCallExpr(Context, OO_Call, NewFn,
|
||||
MethodArgs, NumArgs + 1,
|
||||
ResultTy, RParenLoc));
|
||||
delete [] MethodArgs;
|
||||
|
||||
|
@ -3989,7 +4152,7 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
|
|||
Expr *FnExpr = new (Context) DeclRefExpr(Method, Method->getType(),
|
||||
SourceLocation());
|
||||
UsualUnaryConversions(FnExpr);
|
||||
Base = new (Context) CXXOperatorCallExpr(Context, FnExpr, &Base, 1,
|
||||
Base = new (Context) CXXOperatorCallExpr(Context, OO_Arrow, FnExpr, &Base, 1,
|
||||
Method->getResultType().getNonReferenceType(),
|
||||
OpLoc);
|
||||
return ActOnMemberReferenceExpr(S, ExprArg(*this, Base), OpLoc, tok::arrow,
|
||||
|
|
|
@ -648,9 +648,11 @@ TemplateExprInstantiator::VisitBinaryOperator(BinaryOperator *E) {
|
|||
|
||||
Sema::OwningExprResult
|
||||
TemplateExprInstantiator::VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
|
||||
// FIXME: HACK HACK HACK. This is so utterly and completely wrong
|
||||
// that I don't want to explain it here. I'll just fix it tomorrow
|
||||
// instead.
|
||||
// FIXME: Only handles binary operators at the moment.
|
||||
|
||||
// FIXME: Can we optimize this further if neither the left- nor the
|
||||
// right-hand sides are type-dependent? It depends on whether we
|
||||
// need to perform ADL again
|
||||
Sema::OwningExprResult LHS = Visit(E->getArg(0));
|
||||
if (LHS.isInvalid())
|
||||
return SemaRef.ExprError();
|
||||
|
@ -659,11 +661,56 @@ TemplateExprInstantiator::VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
|
|||
if (RHS.isInvalid())
|
||||
return SemaRef.ExprError();
|
||||
|
||||
Sema::OwningExprResult Result
|
||||
= SemaRef.CreateBuiltinBinOp(E->getOperatorLoc(),
|
||||
BinaryOperator::Add,
|
||||
(Expr *)LHS.get(),
|
||||
(Expr *)RHS.get());
|
||||
Expr *lhs = (Expr *)LHS.get(), *rhs = (Expr *)RHS.get();
|
||||
Expr *Args[2] = { lhs, rhs };
|
||||
|
||||
if (!E->isTypeDependent()) {
|
||||
// Since our original expression was not type-dependent, we do not
|
||||
// perform lookup again at instantiation time (C++ [temp.dep]p1).
|
||||
// Instead, we just build the new overloaded operator call
|
||||
// expression.
|
||||
LHS.release();
|
||||
RHS.release();
|
||||
return SemaRef.Owned(new (SemaRef.Context) CXXOperatorCallExpr(
|
||||
SemaRef.Context,
|
||||
E->getOperator(),
|
||||
E->getCallee(),
|
||||
Args, 2, E->getType(),
|
||||
E->getOperatorLoc()));
|
||||
}
|
||||
|
||||
BinaryOperator::Opcode Opc =
|
||||
BinaryOperator::getOverloadedOpcode(E->getOperator());
|
||||
Sema::OwningExprResult Result(SemaRef);
|
||||
if (!lhs->getType()->isOverloadableType() &&
|
||||
!rhs->getType()->isOverloadableType()) {
|
||||
// Neither LHS nor RHS is an overloadable type, so try create a
|
||||
// built-in binary operation.
|
||||
Result = SemaRef.CreateBuiltinBinOp(E->getOperatorLoc(), Opc,
|
||||
lhs, rhs);
|
||||
} else {
|
||||
// Compute the set of functions that were found at template
|
||||
// definition time.
|
||||
Sema::FunctionSet Functions;
|
||||
DeclRefExpr *DRE = cast<DeclRefExpr>(E->getCallee());
|
||||
OverloadedFunctionDecl *Overloads
|
||||
= cast<OverloadedFunctionDecl>(DRE->getDecl());
|
||||
for (OverloadedFunctionDecl::function_iterator
|
||||
F = Overloads->function_begin(),
|
||||
FEnd = Overloads->function_end();
|
||||
F != FEnd; ++F)
|
||||
Functions.insert(*F);
|
||||
|
||||
// Add any functions found via argument-dependent lookup.
|
||||
DeclarationName OpName
|
||||
= SemaRef.Context.DeclarationNames.getCXXOperatorName(E->getOperator());
|
||||
SemaRef.ArgumentDependentLookup(OpName, Args, 2, Functions);
|
||||
|
||||
// Create the overloaded operator.
|
||||
Result = SemaRef.CreateOverloadedBinOp(E->getOperatorLoc(), Opc,
|
||||
Functions, lhs, rhs);
|
||||
}
|
||||
|
||||
if (Result.isInvalid())
|
||||
return SemaRef.ExprError();
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче