//===--- 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 = nullptr;
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 = nullptr;
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 = nullptr;
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 = nullptr;
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:
case CK_AddressSpaceConversion:
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;
case CK_BooleanToSignedIntegral:
llvm_unreachable("OpenCL-specific cast in Objective-C?");
}
}
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) ||
Sel == NS.getNSStringSelector(NSAPI::NSStr_initWithUTF8String)) {
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;
}