teak-llvm/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
Fariborz Jahanian 56f48d09f8 ObjC migrator: Improve on hueristics.
migrate to 'copy attribute if Object
class implements NSCopying otherwise 
assume implied 'strong'. Remove 
lifetime qualifier on property as it has
moved to property's attribute. Added TODO
comment for future work by poking into
setter implementation.

llvm-svn: 186037
2013-07-10 21:30:22 +00:00

1216 lines
42 KiB
C++

//===--- 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;
}
bool edit::rewriteToObjCProperty(const ObjCMethodDecl *Getter,
const ObjCMethodDecl *Setter,
const NSAPI &NS, Commit &commit) {
ASTContext &Context = NS.getASTContext();
std::string PropertyString = "@property";
const ParmVarDecl *argDecl = *Setter->param_begin();
QualType ArgType = Context.getCanonicalType(argDecl->getType());
Qualifiers::ObjCLifetime propertyLifetime = ArgType.getObjCLifetime();
if (ArgType->isObjCRetainableType() &&
propertyLifetime == Qualifiers::OCL_Strong) {
if (const ObjCObjectPointerType *ObjPtrTy =
ArgType->getAs<ObjCObjectPointerType>()) {
ObjCInterfaceDecl *IDecl = ObjPtrTy->getObjectType()->getInterface();
if (IDecl &&
IDecl->lookupNestedProtocol(&Context.Idents.get("NSCopying")))
PropertyString += "(copy)";
}
}
else if (propertyLifetime == Qualifiers::OCL_Weak)
// TODO. More precise determination of 'weak' attribute requires
// looking into setter's implementation for backing weak ivar.
PropertyString += "(weak)";
else
PropertyString += "(unsafe_unretained)";
// strip off any ARC lifetime qualifier.
QualType CanResultTy = Context.getCanonicalType(Getter->getResultType());
if (CanResultTy.getQualifiers().hasObjCLifetime()) {
Qualifiers Qs = CanResultTy.getQualifiers();
Qs.removeObjCLifetime();
CanResultTy = Context.getQualifiedType(CanResultTy.getUnqualifiedType(), Qs);
}
PropertyString += " ";
PropertyString += CanResultTy.getAsString(Context.getPrintingPolicy());
PropertyString += " ";
PropertyString += Getter->getNameAsString();
commit.replace(CharSourceRange::getCharRange(Getter->getLocStart(),
Getter->getDeclaratorEndLoc()),
PropertyString);
SourceLocation EndLoc = Setter->getDeclaratorEndLoc();
// Get location past ';'
EndLoc = EndLoc.getLocWithOffset(1);
commit.remove(CharSourceRange::getCharRange(Setter->getLocStart(), EndLoc));
return true;
}
/// \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;
}