| //===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Rewrites legacy method calls to modern syntax. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Edit/Rewriters.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ExprCXX.h" |
| #include "clang/AST/ExprObjC.h" |
| #include "clang/AST/NSAPI.h" |
| #include "clang/AST/ParentMap.h" |
| #include "clang/Edit/Commit.h" |
| #include "clang/Lex/Lexer.h" |
| |
| using namespace clang; |
| using namespace edit; |
| |
| static bool checkForLiteralCreation(const ObjCMessageExpr *Msg, |
| IdentifierInfo *&ClassId, |
| const LangOptions &LangOpts) { |
| if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl()) |
| return false; |
| |
| const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface(); |
| if (!Receiver) |
| return false; |
| ClassId = Receiver->getIdentifier(); |
| |
| if (Msg->getReceiverKind() == ObjCMessageExpr::Class) |
| return true; |
| |
| // When in ARC mode we also convert "[[.. alloc] init]" messages to literals, |
| // since the change from +1 to +0 will be handled fine by ARC. |
| if (LangOpts.ObjCAutoRefCount) { |
| if (Msg->getReceiverKind() == ObjCMessageExpr::Instance) { |
| if (const ObjCMessageExpr *Rec = dyn_cast<ObjCMessageExpr>( |
| Msg->getInstanceReceiver()->IgnoreParenImpCasts())) { |
| if (Rec->getMethodFamily() == OMF_alloc) |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteObjCRedundantCallWithLiteral. |
| //===----------------------------------------------------------------------===// |
| |
| bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| IdentifierInfo *II = 0; |
| if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) |
| return false; |
| if (Msg->getNumArgs() != 1) |
| return false; |
| |
| const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); |
| Selector Sel = Msg->getSelector(); |
| |
| if ((isa<ObjCStringLiteral>(Arg) && |
| NS.getNSClassId(NSAPI::ClassId_NSString) == II && |
| (NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel || |
| NS.getNSStringSelector(NSAPI::NSStr_initWithString) == Sel)) || |
| |
| (isa<ObjCArrayLiteral>(Arg) && |
| NS.getNSClassId(NSAPI::ClassId_NSArray) == II && |
| (NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel || |
| NS.getNSArraySelector(NSAPI::NSArr_initWithArray) == Sel)) || |
| |
| (isa<ObjCDictionaryLiteral>(Arg) && |
| NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II && |
| (NS.getNSDictionarySelector( |
| NSAPI::NSDict_dictionaryWithDictionary) == Sel || |
| NS.getNSDictionarySelector(NSAPI::NSDict_initWithDictionary) == Sel))) { |
| |
| commit.replaceWithInner(Msg->getSourceRange(), |
| Msg->getArg(0)->getSourceRange()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToObjCSubscriptSyntax. |
| //===----------------------------------------------------------------------===// |
| |
| /// \brief Check for classes that accept 'objectForKey:' (or the other selectors |
| /// that the migrator handles) but return their instances as 'id', resulting |
| /// in the compiler resolving 'objectForKey:' as the method from NSDictionary. |
| /// |
| /// When checking if we can convert to subscripting syntax, check whether |
| /// the receiver is a result of a class method from a hardcoded list of |
| /// such classes. In such a case return the specific class as the interface |
| /// of the receiver. |
| /// |
| /// FIXME: Remove this when these classes start using 'instancetype'. |
| static const ObjCInterfaceDecl * |
| maybeAdjustInterfaceForSubscriptingCheck(const ObjCInterfaceDecl *IFace, |
| const Expr *Receiver, |
| ASTContext &Ctx) { |
| assert(IFace && Receiver); |
| |
| // If the receiver has type 'id'... |
| if (!Ctx.isObjCIdType(Receiver->getType().getUnqualifiedType())) |
| return IFace; |
| |
| const ObjCMessageExpr * |
| InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver->IgnoreParenCasts()); |
| if (!InnerMsg) |
| return IFace; |
| |
| QualType ClassRec; |
| switch (InnerMsg->getReceiverKind()) { |
| case ObjCMessageExpr::Instance: |
| case ObjCMessageExpr::SuperInstance: |
| return IFace; |
| |
| case ObjCMessageExpr::Class: |
| ClassRec = InnerMsg->getClassReceiver(); |
| break; |
| case ObjCMessageExpr::SuperClass: |
| ClassRec = InnerMsg->getSuperType(); |
| break; |
| } |
| |
| if (ClassRec.isNull()) |
| return IFace; |
| |
| // ...and it is the result of a class message... |
| |
| const ObjCObjectType *ObjTy = ClassRec->getAs<ObjCObjectType>(); |
| if (!ObjTy) |
| return IFace; |
| const ObjCInterfaceDecl *OID = ObjTy->getInterface(); |
| |
| // ...and the receiving class is NSMapTable or NSLocale, return that |
| // class as the receiving interface. |
| if (OID->getName() == "NSMapTable" || |
| OID->getName() == "NSLocale") |
| return OID; |
| |
| return IFace; |
| } |
| |
| static bool canRewriteToSubscriptSyntax(const ObjCInterfaceDecl *&IFace, |
| const ObjCMessageExpr *Msg, |
| ASTContext &Ctx, |
| Selector subscriptSel) { |
| const Expr *Rec = Msg->getInstanceReceiver(); |
| if (!Rec) |
| return false; |
| IFace = maybeAdjustInterfaceForSubscriptingCheck(IFace, Rec, Ctx); |
| |
| if (const ObjCMethodDecl *MD = IFace->lookupInstanceMethod(subscriptSel)) { |
| if (!MD->isUnavailable()) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool subscriptOperatorNeedsParens(const Expr *FullExpr); |
| |
| static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) { |
| if (subscriptOperatorNeedsParens(Receiver)) { |
| SourceRange RecRange = Receiver->getSourceRange(); |
| commit.insertWrap("(", RecRange, ")"); |
| } |
| } |
| |
| static bool rewriteToSubscriptGetCommon(const ObjCMessageExpr *Msg, |
| Commit &commit) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| const Expr *Rec = Msg->getInstanceReceiver(); |
| if (!Rec) |
| return false; |
| |
| SourceRange MsgRange = Msg->getSourceRange(); |
| SourceRange RecRange = Rec->getSourceRange(); |
| SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); |
| |
| commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), |
| ArgRange.getBegin()), |
| CharSourceRange::getTokenRange(RecRange)); |
| commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()), |
| ArgRange); |
| commit.insertWrap("[", ArgRange, "]"); |
| maybePutParensOnReceiver(Rec, commit); |
| return true; |
| } |
| |
| static bool rewriteToArraySubscriptGet(const ObjCInterfaceDecl *IFace, |
| const ObjCMessageExpr *Msg, |
| const NSAPI &NS, |
| Commit &commit) { |
| if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), |
| NS.getObjectAtIndexedSubscriptSelector())) |
| return false; |
| return rewriteToSubscriptGetCommon(Msg, commit); |
| } |
| |
| static bool rewriteToDictionarySubscriptGet(const ObjCInterfaceDecl *IFace, |
| const ObjCMessageExpr *Msg, |
| const NSAPI &NS, |
| Commit &commit) { |
| if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), |
| NS.getObjectForKeyedSubscriptSelector())) |
| return false; |
| return rewriteToSubscriptGetCommon(Msg, commit); |
| } |
| |
| static bool rewriteToArraySubscriptSet(const ObjCInterfaceDecl *IFace, |
| const ObjCMessageExpr *Msg, |
| const NSAPI &NS, |
| Commit &commit) { |
| if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), |
| NS.getSetObjectAtIndexedSubscriptSelector())) |
| return false; |
| |
| if (Msg->getNumArgs() != 2) |
| return false; |
| const Expr *Rec = Msg->getInstanceReceiver(); |
| if (!Rec) |
| return false; |
| |
| SourceRange MsgRange = Msg->getSourceRange(); |
| SourceRange RecRange = Rec->getSourceRange(); |
| SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); |
| SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); |
| |
| commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), |
| Arg0Range.getBegin()), |
| CharSourceRange::getTokenRange(RecRange)); |
| commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(), |
| Arg1Range.getBegin()), |
| CharSourceRange::getTokenRange(Arg0Range)); |
| commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()), |
| Arg1Range); |
| commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(), |
| Arg1Range.getBegin()), |
| "] = "); |
| maybePutParensOnReceiver(Rec, commit); |
| return true; |
| } |
| |
| static bool rewriteToDictionarySubscriptSet(const ObjCInterfaceDecl *IFace, |
| const ObjCMessageExpr *Msg, |
| const NSAPI &NS, |
| Commit &commit) { |
| if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), |
| NS.getSetObjectForKeyedSubscriptSelector())) |
| return false; |
| |
| if (Msg->getNumArgs() != 2) |
| return false; |
| const Expr *Rec = Msg->getInstanceReceiver(); |
| if (!Rec) |
| return false; |
| |
| SourceRange MsgRange = Msg->getSourceRange(); |
| SourceRange RecRange = Rec->getSourceRange(); |
| SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); |
| SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); |
| |
| SourceLocation LocBeforeVal = Arg0Range.getBegin(); |
| commit.insertBefore(LocBeforeVal, "] = "); |
| commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false, |
| /*beforePreviousInsertions=*/true); |
| commit.insertBefore(LocBeforeVal, "["); |
| commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), |
| Arg0Range.getBegin()), |
| CharSourceRange::getTokenRange(RecRange)); |
| commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()), |
| Arg0Range); |
| maybePutParensOnReceiver(Rec, commit); |
| return true; |
| } |
| |
| bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| if (!Msg || Msg->isImplicit() || |
| Msg->getReceiverKind() != ObjCMessageExpr::Instance) |
| return false; |
| const ObjCMethodDecl *Method = Msg->getMethodDecl(); |
| if (!Method) |
| return false; |
| |
| const ObjCInterfaceDecl *IFace = |
| NS.getASTContext().getObjContainingInterface(Method); |
| if (!IFace) |
| return false; |
| Selector Sel = Msg->getSelector(); |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex)) |
| return rewriteToArraySubscriptGet(IFace, Msg, NS, commit); |
| |
| if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey)) |
| return rewriteToDictionarySubscriptGet(IFace, Msg, NS, commit); |
| |
| if (Msg->getNumArgs() != 2) |
| return false; |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex)) |
| return rewriteToArraySubscriptSet(IFace, Msg, NS, commit); |
| |
| if (Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey)) |
| return rewriteToDictionarySubscriptSet(IFace, Msg, NS, commit); |
| |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToObjCLiteralSyntax. |
| //===----------------------------------------------------------------------===// |
| |
| static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit, |
| const ParentMap *PMap); |
| static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit); |
| static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit); |
| static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit); |
| static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit); |
| |
| bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit, |
| const ParentMap *PMap) { |
| IdentifierInfo *II = 0; |
| if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) |
| return false; |
| |
| if (II == NS.getNSClassId(NSAPI::ClassId_NSArray)) |
| return rewriteToArrayLiteral(Msg, NS, commit, PMap); |
| if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary)) |
| return rewriteToDictionaryLiteral(Msg, NS, commit); |
| if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber)) |
| return rewriteToNumberLiteral(Msg, NS, commit); |
| if (II == NS.getNSClassId(NSAPI::ClassId_NSString)) |
| return rewriteToStringBoxedExpression(Msg, NS, commit); |
| |
| return false; |
| } |
| |
| /// \brief Returns true if the immediate message arguments of \c Msg should not |
| /// be rewritten because it will interfere with the rewrite of the parent |
| /// message expression. e.g. |
| /// \code |
| /// [NSDictionary dictionaryWithObjects: |
| /// [NSArray arrayWithObjects:@"1", @"2", nil] |
| /// forKeys:[NSArray arrayWithObjects:@"A", @"B", nil]]; |
| /// \endcode |
| /// It will return true for this because we are going to rewrite this directly |
| /// to a dictionary literal without any array literals. |
| static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg, |
| const NSAPI &NS); |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToArrayLiteral. |
| //===----------------------------------------------------------------------===// |
| |
| /// \brief Adds an explicit cast to 'id' if the type is not objc object. |
| static void objectifyExpr(const Expr *E, Commit &commit); |
| |
| static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit, |
| const ParentMap *PMap) { |
| if (PMap) { |
| const ObjCMessageExpr *ParentMsg = |
| dyn_cast_or_null<ObjCMessageExpr>(PMap->getParentIgnoreParenCasts(Msg)); |
| if (shouldNotRewriteImmediateMessageArgs(ParentMsg, NS)) |
| return false; |
| } |
| |
| Selector Sel = Msg->getSelector(); |
| SourceRange MsgRange = Msg->getSourceRange(); |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) { |
| if (Msg->getNumArgs() != 0) |
| return false; |
| commit.replace(MsgRange, "@[]"); |
| return true; |
| } |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| objectifyExpr(Msg->getArg(0), commit); |
| SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); |
| commit.replaceWithInner(MsgRange, ArgRange); |
| commit.insertWrap("@[", ArgRange, "]"); |
| return true; |
| } |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) || |
| Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) { |
| if (Msg->getNumArgs() == 0) |
| return false; |
| const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); |
| if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) |
| return false; |
| |
| for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i) |
| objectifyExpr(Msg->getArg(i), commit); |
| |
| if (Msg->getNumArgs() == 1) { |
| commit.replace(MsgRange, "@[]"); |
| return true; |
| } |
| SourceRange ArgRange(Msg->getArg(0)->getLocStart(), |
| Msg->getArg(Msg->getNumArgs()-2)->getLocEnd()); |
| commit.replaceWithInner(MsgRange, ArgRange); |
| commit.insertWrap("@[", ArgRange, "]"); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToDictionaryLiteral. |
| //===----------------------------------------------------------------------===// |
| |
| /// \brief If \c Msg is an NSArray creation message or literal, this gets the |
| /// objects that were used to create it. |
| /// \returns true if it is an NSArray and we got objects, or false otherwise. |
| static bool getNSArrayObjects(const Expr *E, const NSAPI &NS, |
| SmallVectorImpl<const Expr *> &Objs) { |
| if (!E) |
| return false; |
| |
| E = E->IgnoreParenCasts(); |
| if (!E) |
| return false; |
| |
| if (const ObjCMessageExpr *Msg = dyn_cast<ObjCMessageExpr>(E)) { |
| IdentifierInfo *Cls = 0; |
| if (!checkForLiteralCreation(Msg, Cls, NS.getASTContext().getLangOpts())) |
| return false; |
| |
| if (Cls != NS.getNSClassId(NSAPI::ClassId_NSArray)) |
| return false; |
| |
| Selector Sel = Msg->getSelector(); |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) |
| return true; // empty array. |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| Objs.push_back(Msg->getArg(0)); |
| return true; |
| } |
| |
| if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) || |
| Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) { |
| if (Msg->getNumArgs() == 0) |
| return false; |
| const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); |
| if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) |
| return false; |
| |
| for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i) |
| Objs.push_back(Msg->getArg(i)); |
| return true; |
| } |
| |
| } else if (const ObjCArrayLiteral *ArrLit = dyn_cast<ObjCArrayLiteral>(E)) { |
| for (unsigned i = 0, e = ArrLit->getNumElements(); i != e; ++i) |
| Objs.push_back(ArrLit->getElement(i)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| Selector Sel = Msg->getSelector(); |
| SourceRange MsgRange = Msg->getSourceRange(); |
| |
| if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) { |
| if (Msg->getNumArgs() != 0) |
| return false; |
| commit.replace(MsgRange, "@{}"); |
| return true; |
| } |
| |
| if (Sel == NS.getNSDictionarySelector( |
| NSAPI::NSDict_dictionaryWithObjectForKey)) { |
| if (Msg->getNumArgs() != 2) |
| return false; |
| |
| objectifyExpr(Msg->getArg(0), commit); |
| objectifyExpr(Msg->getArg(1), commit); |
| |
| SourceRange ValRange = Msg->getArg(0)->getSourceRange(); |
| SourceRange KeyRange = Msg->getArg(1)->getSourceRange(); |
| // Insert key before the value. |
| commit.insertBefore(ValRange.getBegin(), ": "); |
| commit.insertFromRange(ValRange.getBegin(), |
| CharSourceRange::getTokenRange(KeyRange), |
| /*afterToken=*/false, /*beforePreviousInsertions=*/true); |
| commit.insertBefore(ValRange.getBegin(), "@{"); |
| commit.insertAfterToken(ValRange.getEnd(), "}"); |
| commit.replaceWithInner(MsgRange, ValRange); |
| return true; |
| } |
| |
| if (Sel == NS.getNSDictionarySelector( |
| NSAPI::NSDict_dictionaryWithObjectsAndKeys) || |
| Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsAndKeys)) { |
| if (Msg->getNumArgs() % 2 != 1) |
| return false; |
| unsigned SentinelIdx = Msg->getNumArgs() - 1; |
| const Expr *SentinelExpr = Msg->getArg(SentinelIdx); |
| if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) |
| return false; |
| |
| if (Msg->getNumArgs() == 1) { |
| commit.replace(MsgRange, "@{}"); |
| return true; |
| } |
| |
| for (unsigned i = 0; i < SentinelIdx; i += 2) { |
| objectifyExpr(Msg->getArg(i), commit); |
| objectifyExpr(Msg->getArg(i+1), commit); |
| |
| SourceRange ValRange = Msg->getArg(i)->getSourceRange(); |
| SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange(); |
| // Insert value after key. |
| commit.insertAfterToken(KeyRange.getEnd(), ": "); |
| commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); |
| commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(), |
| KeyRange.getBegin())); |
| } |
| // Range of arguments up until and including the last key. |
| // The sentinel and first value are cut off, the value will move after the |
| // key. |
| SourceRange ArgRange(Msg->getArg(1)->getLocStart(), |
| Msg->getArg(SentinelIdx-1)->getLocEnd()); |
| commit.insertWrap("@{", ArgRange, "}"); |
| commit.replaceWithInner(MsgRange, ArgRange); |
| return true; |
| } |
| |
| if (Sel == NS.getNSDictionarySelector( |
| NSAPI::NSDict_dictionaryWithObjectsForKeys) || |
| Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) { |
| if (Msg->getNumArgs() != 2) |
| return false; |
| |
| SmallVector<const Expr *, 8> Vals; |
| if (!getNSArrayObjects(Msg->getArg(0), NS, Vals)) |
| return false; |
| |
| SmallVector<const Expr *, 8> Keys; |
| if (!getNSArrayObjects(Msg->getArg(1), NS, Keys)) |
| return false; |
| |
| if (Vals.size() != Keys.size()) |
| return false; |
| |
| if (Vals.empty()) { |
| commit.replace(MsgRange, "@{}"); |
| return true; |
| } |
| |
| for (unsigned i = 0, n = Vals.size(); i < n; ++i) { |
| objectifyExpr(Vals[i], commit); |
| objectifyExpr(Keys[i], commit); |
| |
| SourceRange ValRange = Vals[i]->getSourceRange(); |
| SourceRange KeyRange = Keys[i]->getSourceRange(); |
| // Insert value after key. |
| commit.insertAfterToken(KeyRange.getEnd(), ": "); |
| commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); |
| } |
| // Range of arguments up until and including the last key. |
| // The first value is cut off, the value will move after the key. |
| SourceRange ArgRange(Keys.front()->getLocStart(), |
| Keys.back()->getLocEnd()); |
| commit.insertWrap("@{", ArgRange, "}"); |
| commit.replaceWithInner(MsgRange, ArgRange); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg, |
| const NSAPI &NS) { |
| if (!Msg) |
| return false; |
| |
| IdentifierInfo *II = 0; |
| if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) |
| return false; |
| |
| if (II != NS.getNSClassId(NSAPI::ClassId_NSDictionary)) |
| return false; |
| |
| Selector Sel = Msg->getSelector(); |
| if (Sel == NS.getNSDictionarySelector( |
| NSAPI::NSDict_dictionaryWithObjectsForKeys) || |
| Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) { |
| if (Msg->getNumArgs() != 2) |
| return false; |
| |
| SmallVector<const Expr *, 8> Vals; |
| if (!getNSArrayObjects(Msg->getArg(0), NS, Vals)) |
| return false; |
| |
| SmallVector<const Expr *, 8> Keys; |
| if (!getNSArrayObjects(Msg->getArg(1), NS, Keys)) |
| return false; |
| |
| if (Vals.size() != Keys.size()) |
| return false; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToNumberLiteral. |
| //===----------------------------------------------------------------------===// |
| |
| static bool rewriteToCharLiteral(const ObjCMessageExpr *Msg, |
| const CharacterLiteral *Arg, |
| const NSAPI &NS, Commit &commit) { |
| if (Arg->getKind() != CharacterLiteral::Ascii) |
| return false; |
| if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithChar, |
| Msg->getSelector())) { |
| SourceRange ArgRange = Arg->getSourceRange(); |
| commit.replaceWithInner(Msg->getSourceRange(), ArgRange); |
| commit.insert(ArgRange.getBegin(), "@"); |
| return true; |
| } |
| |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| } |
| |
| static bool rewriteToBoolLiteral(const ObjCMessageExpr *Msg, |
| const Expr *Arg, |
| const NSAPI &NS, Commit &commit) { |
| if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithBool, |
| Msg->getSelector())) { |
| SourceRange ArgRange = Arg->getSourceRange(); |
| commit.replaceWithInner(Msg->getSourceRange(), ArgRange); |
| commit.insert(ArgRange.getBegin(), "@"); |
| return true; |
| } |
| |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| } |
| |
| namespace { |
| |
| struct LiteralInfo { |
| bool Hex, Octal; |
| StringRef U, F, L, LL; |
| CharSourceRange WithoutSuffRange; |
| }; |
| |
| } |
| |
| static bool getLiteralInfo(SourceRange literalRange, |
| bool isFloat, bool isIntZero, |
| ASTContext &Ctx, LiteralInfo &Info) { |
| if (literalRange.getBegin().isMacroID() || |
| literalRange.getEnd().isMacroID()) |
| return false; |
| StringRef text = Lexer::getSourceText( |
| CharSourceRange::getTokenRange(literalRange), |
| Ctx.getSourceManager(), Ctx.getLangOpts()); |
| if (text.empty()) |
| return false; |
| |
| Optional<bool> UpperU, UpperL; |
| bool UpperF = false; |
| |
| struct Suff { |
| static bool has(StringRef suff, StringRef &text) { |
| if (text.endswith(suff)) { |
| text = text.substr(0, text.size()-suff.size()); |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| while (1) { |
| if (Suff::has("u", text)) { |
| UpperU = false; |
| } else if (Suff::has("U", text)) { |
| UpperU = true; |
| } else if (Suff::has("ll", text)) { |
| UpperL = false; |
| } else if (Suff::has("LL", text)) { |
| UpperL = true; |
| } else if (Suff::has("l", text)) { |
| UpperL = false; |
| } else if (Suff::has("L", text)) { |
| UpperL = true; |
| } else if (isFloat && Suff::has("f", text)) { |
| UpperF = false; |
| } else if (isFloat && Suff::has("F", text)) { |
| UpperF = true; |
| } else |
| break; |
| } |
| |
| if (!UpperU.hasValue() && !UpperL.hasValue()) |
| UpperU = UpperL = true; |
| else if (UpperU.hasValue() && !UpperL.hasValue()) |
| UpperL = UpperU; |
| else if (UpperL.hasValue() && !UpperU.hasValue()) |
| UpperU = UpperL; |
| |
| Info.U = *UpperU ? "U" : "u"; |
| Info.L = *UpperL ? "L" : "l"; |
| Info.LL = *UpperL ? "LL" : "ll"; |
| Info.F = UpperF ? "F" : "f"; |
| |
| Info.Hex = Info.Octal = false; |
| if (text.startswith("0x")) |
| Info.Hex = true; |
| else if (!isFloat && !isIntZero && text.startswith("0")) |
| Info.Octal = true; |
| |
| SourceLocation B = literalRange.getBegin(); |
| Info.WithoutSuffRange = |
| CharSourceRange::getCharRange(B, B.getLocWithOffset(text.size())); |
| return true; |
| } |
| |
| static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| |
| const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); |
| if (const CharacterLiteral *CharE = dyn_cast<CharacterLiteral>(Arg)) |
| return rewriteToCharLiteral(Msg, CharE, NS, commit); |
| if (const ObjCBoolLiteralExpr *BE = dyn_cast<ObjCBoolLiteralExpr>(Arg)) |
| return rewriteToBoolLiteral(Msg, BE, NS, commit); |
| if (const CXXBoolLiteralExpr *BE = dyn_cast<CXXBoolLiteralExpr>(Arg)) |
| return rewriteToBoolLiteral(Msg, BE, NS, commit); |
| |
| const Expr *literalE = Arg; |
| if (const UnaryOperator *UOE = dyn_cast<UnaryOperator>(literalE)) { |
| if (UOE->getOpcode() == UO_Plus || UOE->getOpcode() == UO_Minus) |
| literalE = UOE->getSubExpr(); |
| } |
| |
| // Only integer and floating literals, otherwise try to rewrite to boxed |
| // expression. |
| if (!isa<IntegerLiteral>(literalE) && !isa<FloatingLiteral>(literalE)) |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| ASTContext &Ctx = NS.getASTContext(); |
| Selector Sel = Msg->getSelector(); |
| Optional<NSAPI::NSNumberLiteralMethodKind> |
| MKOpt = NS.getNSNumberLiteralMethodKind(Sel); |
| if (!MKOpt) |
| return false; |
| NSAPI::NSNumberLiteralMethodKind MK = *MKOpt; |
| |
| bool CallIsUnsigned = false, CallIsLong = false, CallIsLongLong = false; |
| bool CallIsFloating = false, CallIsDouble = false; |
| |
| switch (MK) { |
| // We cannot have these calls with int/float literals. |
| case NSAPI::NSNumberWithChar: |
| case NSAPI::NSNumberWithUnsignedChar: |
| case NSAPI::NSNumberWithShort: |
| case NSAPI::NSNumberWithUnsignedShort: |
| case NSAPI::NSNumberWithBool: |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| case NSAPI::NSNumberWithUnsignedInt: |
| case NSAPI::NSNumberWithUnsignedInteger: |
| CallIsUnsigned = true; |
| case NSAPI::NSNumberWithInt: |
| case NSAPI::NSNumberWithInteger: |
| break; |
| |
| case NSAPI::NSNumberWithUnsignedLong: |
| CallIsUnsigned = true; |
| case NSAPI::NSNumberWithLong: |
| CallIsLong = true; |
| break; |
| |
| case NSAPI::NSNumberWithUnsignedLongLong: |
| CallIsUnsigned = true; |
| case NSAPI::NSNumberWithLongLong: |
| CallIsLongLong = true; |
| break; |
| |
| case NSAPI::NSNumberWithDouble: |
| CallIsDouble = true; |
| case NSAPI::NSNumberWithFloat: |
| CallIsFloating = true; |
| break; |
| } |
| |
| SourceRange ArgRange = Arg->getSourceRange(); |
| QualType ArgTy = Arg->getType(); |
| QualType CallTy = Msg->getArg(0)->getType(); |
| |
| // Check for the easy case, the literal maps directly to the call. |
| if (Ctx.hasSameType(ArgTy, CallTy)) { |
| commit.replaceWithInner(Msg->getSourceRange(), ArgRange); |
| commit.insert(ArgRange.getBegin(), "@"); |
| return true; |
| } |
| |
| // We will need to modify the literal suffix to get the same type as the call. |
| // Try with boxed expression if it came from a macro. |
| if (ArgRange.getBegin().isMacroID()) |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| bool LitIsFloat = ArgTy->isFloatingType(); |
| // For a float passed to integer call, don't try rewriting to objc literal. |
| // It is difficult and a very uncommon case anyway. |
| // But try with boxed expression. |
| if (LitIsFloat && !CallIsFloating) |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| // Try to modify the literal make it the same type as the method call. |
| // -Modify the suffix, and/or |
| // -Change integer to float |
| |
| LiteralInfo LitInfo; |
| bool isIntZero = false; |
| if (const IntegerLiteral *IntE = dyn_cast<IntegerLiteral>(literalE)) |
| isIntZero = !IntE->getValue().getBoolValue(); |
| if (!getLiteralInfo(ArgRange, LitIsFloat, isIntZero, Ctx, LitInfo)) |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| // Not easy to do int -> float with hex/octal and uncommon anyway. |
| if (!LitIsFloat && CallIsFloating && (LitInfo.Hex || LitInfo.Octal)) |
| return rewriteToNumericBoxedExpression(Msg, NS, commit); |
| |
| SourceLocation LitB = LitInfo.WithoutSuffRange.getBegin(); |
| SourceLocation LitE = LitInfo.WithoutSuffRange.getEnd(); |
| |
| commit.replaceWithInner(CharSourceRange::getTokenRange(Msg->getSourceRange()), |
| LitInfo.WithoutSuffRange); |
| commit.insert(LitB, "@"); |
| |
| if (!LitIsFloat && CallIsFloating) |
| commit.insert(LitE, ".0"); |
| |
| if (CallIsFloating) { |
| if (!CallIsDouble) |
| commit.insert(LitE, LitInfo.F); |
| } else { |
| if (CallIsUnsigned) |
| commit.insert(LitE, LitInfo.U); |
| |
| if (CallIsLong) |
| commit.insert(LitE, LitInfo.L); |
| else if (CallIsLongLong) |
| commit.insert(LitE, LitInfo.LL); |
| } |
| return true; |
| } |
| |
| // FIXME: Make determination of operator precedence more general and |
| // make it broadly available. |
| static bool subscriptOperatorNeedsParens(const Expr *FullExpr) { |
| const Expr* Expr = FullExpr->IgnoreImpCasts(); |
| if (isa<ArraySubscriptExpr>(Expr) || |
| isa<CallExpr>(Expr) || |
| isa<DeclRefExpr>(Expr) || |
| isa<CXXNamedCastExpr>(Expr) || |
| isa<CXXConstructExpr>(Expr) || |
| isa<CXXThisExpr>(Expr) || |
| isa<CXXTypeidExpr>(Expr) || |
| isa<CXXUnresolvedConstructExpr>(Expr) || |
| isa<ObjCMessageExpr>(Expr) || |
| isa<ObjCPropertyRefExpr>(Expr) || |
| isa<ObjCProtocolExpr>(Expr) || |
| isa<MemberExpr>(Expr) || |
| isa<ObjCIvarRefExpr>(Expr) || |
| isa<ParenExpr>(FullExpr) || |
| isa<ParenListExpr>(Expr) || |
| isa<SizeOfPackExpr>(Expr)) |
| return false; |
| |
| return true; |
| } |
| static bool castOperatorNeedsParens(const Expr *FullExpr) { |
| const Expr* Expr = FullExpr->IgnoreImpCasts(); |
| if (isa<ArraySubscriptExpr>(Expr) || |
| isa<CallExpr>(Expr) || |
| isa<DeclRefExpr>(Expr) || |
| isa<CastExpr>(Expr) || |
| isa<CXXNewExpr>(Expr) || |
| isa<CXXConstructExpr>(Expr) || |
| isa<CXXDeleteExpr>(Expr) || |
| isa<CXXNoexceptExpr>(Expr) || |
| isa<CXXPseudoDestructorExpr>(Expr) || |
| isa<CXXScalarValueInitExpr>(Expr) || |
| isa<CXXThisExpr>(Expr) || |
| isa<CXXTypeidExpr>(Expr) || |
| isa<CXXUnresolvedConstructExpr>(Expr) || |
| isa<ObjCMessageExpr>(Expr) || |
| isa<ObjCPropertyRefExpr>(Expr) || |
| isa<ObjCProtocolExpr>(Expr) || |
| isa<MemberExpr>(Expr) || |
| isa<ObjCIvarRefExpr>(Expr) || |
| isa<ParenExpr>(FullExpr) || |
| isa<ParenListExpr>(Expr) || |
| isa<SizeOfPackExpr>(Expr) || |
| isa<UnaryOperator>(Expr)) |
| return false; |
| |
| return true; |
| } |
| |
| static void objectifyExpr(const Expr *E, Commit &commit) { |
| if (!E) return; |
| |
| QualType T = E->getType(); |
| if (T->isObjCObjectPointerType()) { |
| if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { |
| if (ICE->getCastKind() != CK_CPointerToObjCPointerCast) |
| return; |
| } else { |
| return; |
| } |
| } else if (!T->isPointerType()) { |
| return; |
| } |
| |
| SourceRange Range = E->getSourceRange(); |
| if (castOperatorNeedsParens(E)) |
| commit.insertWrap("(", Range, ")"); |
| commit.insertBefore(Range.getBegin(), "(id)"); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToNumericBoxedExpression. |
| //===----------------------------------------------------------------------===// |
| |
| static bool isEnumConstant(const Expr *E) { |
| if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParenImpCasts())) |
| if (const ValueDecl *VD = DRE->getDecl()) |
| return isa<EnumConstantDecl>(VD); |
| |
| return false; |
| } |
| |
| static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| |
| const Expr *Arg = Msg->getArg(0); |
| if (Arg->isTypeDependent()) |
| return false; |
| |
| ASTContext &Ctx = NS.getASTContext(); |
| Selector Sel = Msg->getSelector(); |
| Optional<NSAPI::NSNumberLiteralMethodKind> |
| MKOpt = NS.getNSNumberLiteralMethodKind(Sel); |
| if (!MKOpt) |
| return false; |
| NSAPI::NSNumberLiteralMethodKind MK = *MKOpt; |
| |
| const Expr *OrigArg = Arg->IgnoreImpCasts(); |
| QualType FinalTy = Arg->getType(); |
| QualType OrigTy = OrigArg->getType(); |
| uint64_t FinalTySize = Ctx.getTypeSize(FinalTy); |
| uint64_t OrigTySize = Ctx.getTypeSize(OrigTy); |
| |
| bool isTruncated = FinalTySize < OrigTySize; |
| bool needsCast = false; |
| |
| if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(Arg)) { |
| switch (ICE->getCastKind()) { |
| case CK_LValueToRValue: |
| case CK_NoOp: |
| case CK_UserDefinedConversion: |
| break; |
| |
| case CK_IntegralCast: { |
| if (MK == NSAPI::NSNumberWithBool && OrigTy->isBooleanType()) |
| break; |
| // Be more liberal with Integer/UnsignedInteger which are very commonly |
| // used. |
| if ((MK == NSAPI::NSNumberWithInteger || |
| MK == NSAPI::NSNumberWithUnsignedInteger) && |
| !isTruncated) { |
| if (OrigTy->getAs<EnumType>() || isEnumConstant(OrigArg)) |
| break; |
| if ((MK==NSAPI::NSNumberWithInteger) == OrigTy->isSignedIntegerType() && |
| OrigTySize >= Ctx.getTypeSize(Ctx.IntTy)) |
| break; |
| } |
| |
| needsCast = true; |
| break; |
| } |
| |
| case CK_PointerToBoolean: |
| case CK_IntegralToBoolean: |
| case CK_IntegralToFloating: |
| case CK_FloatingToIntegral: |
| case CK_FloatingToBoolean: |
| case CK_FloatingCast: |
| case CK_FloatingComplexToReal: |
| case CK_FloatingComplexToBoolean: |
| case CK_IntegralComplexToReal: |
| case CK_IntegralComplexToBoolean: |
| case CK_AtomicToNonAtomic: |
| needsCast = true; |
| break; |
| |
| case CK_Dependent: |
| case CK_BitCast: |
| case CK_LValueBitCast: |
| case CK_BaseToDerived: |
| case CK_DerivedToBase: |
| case CK_UncheckedDerivedToBase: |
| case CK_Dynamic: |
| case CK_ToUnion: |
| case CK_ArrayToPointerDecay: |
| case CK_FunctionToPointerDecay: |
| case CK_NullToPointer: |
| case CK_NullToMemberPointer: |
| case CK_BaseToDerivedMemberPointer: |
| case CK_DerivedToBaseMemberPointer: |
| case CK_MemberPointerToBoolean: |
| case CK_ReinterpretMemberPointer: |
| case CK_ConstructorConversion: |
| case CK_IntegralToPointer: |
| case CK_PointerToIntegral: |
| case CK_ToVoid: |
| case CK_VectorSplat: |
| case CK_CPointerToObjCPointerCast: |
| case CK_BlockPointerToObjCPointerCast: |
| case CK_AnyPointerToBlockPointerCast: |
| case CK_ObjCObjectLValueCast: |
| case CK_FloatingRealToComplex: |
| case CK_FloatingComplexCast: |
| case CK_FloatingComplexToIntegralComplex: |
| case CK_IntegralRealToComplex: |
| case CK_IntegralComplexCast: |
| case CK_IntegralComplexToFloatingComplex: |
| case CK_ARCProduceObject: |
| case CK_ARCConsumeObject: |
| case CK_ARCReclaimReturnedObject: |
| case CK_ARCExtendBlockObject: |
| case CK_NonAtomicToAtomic: |
| case CK_CopyAndAutoreleaseBlockObject: |
| case CK_BuiltinFnToFnPtr: |
| case CK_ZeroToOCLEvent: |
| return false; |
| } |
| } |
| |
| if (needsCast) { |
| DiagnosticsEngine &Diags = Ctx.getDiagnostics(); |
| // FIXME: Use a custom category name to distinguish migration diagnostics. |
| unsigned diagID = Diags.getCustomDiagID(DiagnosticsEngine::Warning, |
| "converting to boxing syntax requires casting %0 to %1"); |
| Diags.Report(Msg->getExprLoc(), diagID) << OrigTy << FinalTy |
| << Msg->getSourceRange(); |
| return false; |
| } |
| |
| SourceRange ArgRange = OrigArg->getSourceRange(); |
| commit.replaceWithInner(Msg->getSourceRange(), ArgRange); |
| |
| if (isa<ParenExpr>(OrigArg) || isa<IntegerLiteral>(OrigArg)) |
| commit.insertBefore(ArgRange.getBegin(), "@"); |
| else |
| commit.insertWrap("@(", ArgRange, ")"); |
| |
| return true; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // rewriteToStringBoxedExpression. |
| //===----------------------------------------------------------------------===// |
| |
| static bool doRewriteToUTF8StringBoxedExpressionHelper( |
| const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| const Expr *Arg = Msg->getArg(0); |
| if (Arg->isTypeDependent()) |
| return false; |
| |
| ASTContext &Ctx = NS.getASTContext(); |
| |
| const Expr *OrigArg = Arg->IgnoreImpCasts(); |
| QualType OrigTy = OrigArg->getType(); |
| if (OrigTy->isArrayType()) |
| OrigTy = Ctx.getArrayDecayedType(OrigTy); |
| |
| if (const StringLiteral * |
| StrE = dyn_cast<StringLiteral>(OrigArg->IgnoreParens())) { |
| commit.replaceWithInner(Msg->getSourceRange(), StrE->getSourceRange()); |
| commit.insert(StrE->getLocStart(), "@"); |
| return true; |
| } |
| |
| if (const PointerType *PT = OrigTy->getAs<PointerType>()) { |
| QualType PointeeType = PT->getPointeeType(); |
| if (Ctx.hasSameUnqualifiedType(PointeeType, Ctx.CharTy)) { |
| SourceRange ArgRange = OrigArg->getSourceRange(); |
| commit.replaceWithInner(Msg->getSourceRange(), ArgRange); |
| |
| if (isa<ParenExpr>(OrigArg) || isa<IntegerLiteral>(OrigArg)) |
| commit.insertBefore(ArgRange.getBegin(), "@"); |
| else |
| commit.insertWrap("@(", ArgRange, ")"); |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg, |
| const NSAPI &NS, Commit &commit) { |
| Selector Sel = Msg->getSelector(); |
| |
| if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithUTF8String) || |
| Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCString)) { |
| if (Msg->getNumArgs() != 1) |
| return false; |
| return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit); |
| } |
| |
| if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCStringEncoding)) { |
| if (Msg->getNumArgs() != 2) |
| return false; |
| |
| const Expr *encodingArg = Msg->getArg(1); |
| if (NS.isNSUTF8StringEncodingConstant(encodingArg) || |
| NS.isNSASCIIStringEncodingConstant(encodingArg)) |
| return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit); |
| } |
| |
| return false; |
| } |