RewriteObjCFoundationAPI.cpp revision bea6c0a2e77a78e3d27ee44461613acdd1d8ee9c
1//===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// Rewrites legacy method calls to modern syntax.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Edit/Rewriters.h"
15#include "clang/Edit/Commit.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/AST/ExprObjC.h"
18#include "clang/AST/ExprCXX.h"
19#include "clang/AST/NSAPI.h"
20
21using namespace clang;
22using namespace edit;
23
24static bool checkForLiteralCreation(const ObjCMessageExpr *Msg,
25                                    IdentifierInfo *&ClassId) {
26  if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl())
27    return false;
28
29  const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface();
30  if (!Receiver)
31    return false;
32  ClassId = Receiver->getIdentifier();
33
34  if (Msg->getReceiverKind() == ObjCMessageExpr::Class)
35    return true;
36
37  return false;
38}
39
40//===----------------------------------------------------------------------===//
41// rewriteObjCRedundantCallWithLiteral.
42//===----------------------------------------------------------------------===//
43
44bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg,
45                                              const NSAPI &NS, Commit &commit) {
46  IdentifierInfo *II = 0;
47  if (!checkForLiteralCreation(Msg, II))
48    return false;
49  if (Msg->getNumArgs() != 1)
50    return false;
51
52  const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts();
53  Selector Sel = Msg->getSelector();
54
55  if ((isa<ObjCStringLiteral>(Arg) &&
56       NS.getNSClassId(NSAPI::ClassId_NSString) == II &&
57       NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel)    ||
58
59      (isa<ObjCArrayLiteral>(Arg) &&
60       NS.getNSClassId(NSAPI::ClassId_NSArray) == II &&
61       NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel)      ||
62
63      (isa<ObjCDictionaryLiteral>(Arg) &&
64       NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II &&
65       NS.getNSDictionarySelector(
66                              NSAPI::NSDict_dictionaryWithDictionary) == Sel)) {
67
68    commit.replaceWithInner(Msg->getSourceRange(),
69                           Msg->getArg(0)->getSourceRange());
70    return true;
71  }
72
73  return false;
74}
75
76//===----------------------------------------------------------------------===//
77// rewriteToObjCSubscriptSyntax.
78//===----------------------------------------------------------------------===//
79
80static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) {
81  Receiver = Receiver->IgnoreImpCasts();
82  if (isa<BinaryOperator>(Receiver) || isa<UnaryOperator>(Receiver)) {
83    SourceRange RecRange = Receiver->getSourceRange();
84    commit.insertWrap("(", RecRange, ")");
85  }
86}
87
88static bool rewriteToSubscriptGet(const ObjCMessageExpr *Msg, Commit &commit) {
89  if (Msg->getNumArgs() != 1)
90    return false;
91  const Expr *Rec = Msg->getInstanceReceiver();
92  if (!Rec)
93    return false;
94
95  SourceRange MsgRange = Msg->getSourceRange();
96  SourceRange RecRange = Rec->getSourceRange();
97  SourceRange ArgRange = Msg->getArg(0)->getSourceRange();
98
99  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),
100                                                       ArgRange.getBegin()),
101                         CharSourceRange::getTokenRange(RecRange));
102  commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()),
103                         ArgRange);
104  commit.insertWrap("[", ArgRange, "]");
105  maybePutParensOnReceiver(Rec, commit);
106  return true;
107}
108
109static bool rewriteToArraySubscriptSet(const ObjCMessageExpr *Msg,
110                                       Commit &commit) {
111  if (Msg->getNumArgs() != 2)
112    return false;
113  const Expr *Rec = Msg->getInstanceReceiver();
114  if (!Rec)
115    return false;
116
117  SourceRange MsgRange = Msg->getSourceRange();
118  SourceRange RecRange = Rec->getSourceRange();
119  SourceRange Arg0Range = Msg->getArg(0)->getSourceRange();
120  SourceRange Arg1Range = Msg->getArg(1)->getSourceRange();
121
122  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),
123                                                       Arg0Range.getBegin()),
124                         CharSourceRange::getTokenRange(RecRange));
125  commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(),
126                                                       Arg1Range.getBegin()),
127                         CharSourceRange::getTokenRange(Arg0Range));
128  commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()),
129                         Arg1Range);
130  commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(),
131                                                       Arg1Range.getBegin()),
132                    "] = ");
133  maybePutParensOnReceiver(Rec, commit);
134  return true;
135}
136
137static bool rewriteToDictionarySubscriptSet(const ObjCMessageExpr *Msg,
138                                            Commit &commit) {
139  if (Msg->getNumArgs() != 2)
140    return false;
141  const Expr *Rec = Msg->getInstanceReceiver();
142  if (!Rec)
143    return false;
144
145  SourceRange MsgRange = Msg->getSourceRange();
146  SourceRange RecRange = Rec->getSourceRange();
147  SourceRange Arg0Range = Msg->getArg(0)->getSourceRange();
148  SourceRange Arg1Range = Msg->getArg(1)->getSourceRange();
149
150  SourceLocation LocBeforeVal = Arg0Range.getBegin();
151  commit.insertBefore(LocBeforeVal, "] = ");
152  commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false,
153                         /*beforePreviousInsertions=*/true);
154  commit.insertBefore(LocBeforeVal, "[");
155  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),
156                                                       Arg0Range.getBegin()),
157                         CharSourceRange::getTokenRange(RecRange));
158  commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()),
159                         Arg0Range);
160  maybePutParensOnReceiver(Rec, commit);
161  return true;
162}
163
164bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg,
165                                           const NSAPI &NS, Commit &commit) {
166  if (!Msg || Msg->isImplicit() ||
167      Msg->getReceiverKind() != ObjCMessageExpr::Instance)
168    return false;
169  const ObjCMethodDecl *Method = Msg->getMethodDecl();
170  if (!Method)
171    return false;
172
173  const ObjCInterfaceDecl *
174    IFace = NS.getASTContext().getObjContainingInterface(
175                                          const_cast<ObjCMethodDecl *>(Method));
176  if (!IFace)
177    return false;
178  IdentifierInfo *II = IFace->getIdentifier();
179  Selector Sel = Msg->getSelector();
180
181  if ((II == NS.getNSClassId(NSAPI::ClassId_NSArray) &&
182       Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex)) ||
183      (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary) &&
184       Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey)))
185    return rewriteToSubscriptGet(Msg, commit);
186
187  if (Msg->getNumArgs() != 2)
188    return false;
189
190  if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableArray) &&
191      Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex))
192    return rewriteToArraySubscriptSet(Msg, commit);
193
194  if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableDictionary) &&
195      Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey))
196    return rewriteToDictionarySubscriptSet(Msg, commit);
197
198  return false;
199}
200
201//===----------------------------------------------------------------------===//
202// rewriteToObjCLiteralSyntax.
203//===----------------------------------------------------------------------===//
204
205static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg,
206                                  const NSAPI &NS, Commit &commit);
207static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg,
208                                  const NSAPI &NS, Commit &commit);
209static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg,
210                                  const NSAPI &NS, Commit &commit);
211
212bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg,
213                                      const NSAPI &NS, Commit &commit) {
214  IdentifierInfo *II = 0;
215  if (!checkForLiteralCreation(Msg, II))
216    return false;
217
218  if (II == NS.getNSClassId(NSAPI::ClassId_NSArray))
219    return rewriteToArrayLiteral(Msg, NS, commit);
220  if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary))
221    return rewriteToDictionaryLiteral(Msg, NS, commit);
222  if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber))
223    return rewriteToNumberLiteral(Msg, NS, commit);
224
225  return false;
226}
227
228//===----------------------------------------------------------------------===//
229// rewriteToArrayLiteral.
230//===----------------------------------------------------------------------===//
231
232static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg,
233                                  const NSAPI &NS, Commit &commit) {
234  Selector Sel = Msg->getSelector();
235  SourceRange MsgRange = Msg->getSourceRange();
236
237  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) {
238    if (Msg->getNumArgs() != 0)
239      return false;
240    commit.replace(MsgRange, "@[]");
241    return true;
242  }
243
244  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) {
245    if (Msg->getNumArgs() != 1)
246      return false;
247    SourceRange ArgRange = Msg->getArg(0)->getSourceRange();
248    commit.replaceWithInner(MsgRange, ArgRange);
249    commit.insertWrap("@[", ArgRange, "]");
250    return true;
251  }
252
253  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects)) {
254    if (Msg->getNumArgs() == 0)
255      return false;
256    const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1);
257    if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr))
258      return false;
259
260    if (Msg->getNumArgs() == 1) {
261      commit.replace(MsgRange, "@[]");
262      return true;
263    }
264    SourceRange ArgRange(Msg->getArg(0)->getLocStart(),
265                         Msg->getArg(Msg->getNumArgs()-2)->getLocEnd());
266    commit.replaceWithInner(MsgRange, ArgRange);
267    commit.insertWrap("@[", ArgRange, "]");
268    return true;
269  }
270
271  return false;
272}
273
274//===----------------------------------------------------------------------===//
275// rewriteToDictionaryLiteral.
276//===----------------------------------------------------------------------===//
277
278static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg,
279                                       const NSAPI &NS, Commit &commit) {
280  Selector Sel = Msg->getSelector();
281  SourceRange MsgRange = Msg->getSourceRange();
282
283  if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) {
284    if (Msg->getNumArgs() != 0)
285      return false;
286    commit.replace(MsgRange, "@{}");
287    return true;
288  }
289
290  if (Sel == NS.getNSDictionarySelector(
291                                    NSAPI::NSDict_dictionaryWithObjectForKey)) {
292    if (Msg->getNumArgs() != 2)
293      return false;
294    SourceRange ValRange = Msg->getArg(0)->getSourceRange();
295    SourceRange KeyRange = Msg->getArg(1)->getSourceRange();
296    // Insert key before the value.
297    commit.insertBefore(ValRange.getBegin(), ": ");
298    commit.insertFromRange(ValRange.getBegin(),
299                           CharSourceRange::getTokenRange(KeyRange),
300                       /*afterToken=*/false, /*beforePreviousInsertions=*/true);
301    commit.insertBefore(ValRange.getBegin(), "@{");
302    commit.insertAfterToken(ValRange.getEnd(), "}");
303    commit.replaceWithInner(MsgRange, ValRange);
304    return true;
305  }
306
307  if (Sel == NS.getNSDictionarySelector(
308                                  NSAPI::NSDict_dictionaryWithObjectsAndKeys)) {
309    if (Msg->getNumArgs() % 2 != 1)
310      return false;
311    unsigned SentinelIdx = Msg->getNumArgs() - 1;
312    const Expr *SentinelExpr = Msg->getArg(SentinelIdx);
313    if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr))
314      return false;
315
316    if (Msg->getNumArgs() == 1) {
317      commit.replace(MsgRange, "@{}");
318      return true;
319    }
320
321    for (unsigned i = 0; i < SentinelIdx; i += 2) {
322      SourceRange ValRange = Msg->getArg(i)->getSourceRange();
323      SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange();
324      // Insert value after key.
325      commit.insertAfterToken(KeyRange.getEnd(), ": ");
326      commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true);
327      commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(),
328                                                  KeyRange.getBegin()));
329    }
330    // Range of arguments up until and including the last key.
331    // The sentinel and first value are cut off, the value will move after the
332    // key.
333    SourceRange ArgRange(Msg->getArg(1)->getLocStart(),
334                         Msg->getArg(SentinelIdx-1)->getLocEnd());
335    commit.insertWrap("@{", ArgRange, "}");
336    commit.replaceWithInner(MsgRange, ArgRange);
337    return true;
338  }
339
340  return false;
341}
342
343//===----------------------------------------------------------------------===//
344// rewriteToNumberLiteral.
345//===----------------------------------------------------------------------===//
346
347static bool rewriteToCharLiteral(const ObjCMessageExpr *Msg,
348                                   const CharacterLiteral *Arg,
349                                   const NSAPI &NS, Commit &commit) {
350  if (Arg->getKind() != CharacterLiteral::Ascii)
351    return false;
352  if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithChar,
353                                   Msg->getSelector())) {
354    SourceRange ArgRange = Arg->getSourceRange();
355    commit.replaceWithInner(Msg->getSourceRange(), ArgRange);
356    commit.insert(ArgRange.getBegin(), "@");
357    return true;
358  }
359
360  return false;
361}
362
363static bool rewriteToBoolLiteral(const ObjCMessageExpr *Msg,
364                                   const Expr *Arg,
365                                   const NSAPI &NS, Commit &commit) {
366  if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithBool,
367                                   Msg->getSelector())) {
368    SourceRange ArgRange = Arg->getSourceRange();
369    commit.replaceWithInner(Msg->getSourceRange(), ArgRange);
370    commit.insert(ArgRange.getBegin(), "@");
371    return true;
372  }
373
374  return false;
375}
376
377namespace {
378
379struct LiteralInfo {
380  bool Hex, Octal;
381  StringRef U, F, L, LL;
382  CharSourceRange WithoutSuffRange;
383};
384
385}
386
387static bool getLiteralInfo(SourceRange literalRange,
388                           bool isFloat, bool isIntZero,
389                          ASTContext &Ctx, LiteralInfo &Info) {
390  if (literalRange.getBegin().isMacroID() ||
391      literalRange.getEnd().isMacroID())
392    return false;
393  StringRef text = Lexer::getSourceText(
394                                  CharSourceRange::getTokenRange(literalRange),
395                                  Ctx.getSourceManager(), Ctx.getLangOpts());
396  if (text.empty())
397    return false;
398
399  llvm::Optional<bool> UpperU, UpperL;
400  bool UpperF = false;
401
402  struct Suff {
403    static bool has(StringRef suff, StringRef &text) {
404      if (text.endswith(suff)) {
405        text = text.substr(0, text.size()-suff.size());
406        return true;
407      }
408      return false;
409    }
410  };
411
412  while (1) {
413    if (Suff::has("u", text)) {
414      UpperU = false;
415    } else if (Suff::has("U", text)) {
416      UpperU = true;
417    } else if (Suff::has("ll", text)) {
418      UpperL = false;
419    } else if (Suff::has("LL", text)) {
420      UpperL = true;
421    } else if (Suff::has("l", text)) {
422      UpperL = false;
423    } else if (Suff::has("L", text)) {
424      UpperL = true;
425    } else if (isFloat && Suff::has("f", text)) {
426      UpperF = false;
427    } else if (isFloat && Suff::has("F", text)) {
428      UpperF = true;
429    } else
430      break;
431  }
432
433  if (!UpperU.hasValue() && !UpperL.hasValue())
434    UpperU = UpperL = true;
435  else if (UpperU.hasValue() && !UpperL.hasValue())
436    UpperL = UpperU;
437  else if (UpperL.hasValue() && !UpperU.hasValue())
438    UpperU = UpperL;
439
440  Info.U = *UpperU ? "U" : "u";
441  Info.L = *UpperL ? "L" : "l";
442  Info.LL = *UpperL ? "LL" : "ll";
443  Info.F = UpperF ? "F" : "f";
444
445  Info.Hex = Info.Octal = false;
446  if (text.startswith("0x"))
447    Info.Hex = true;
448  else if (!isFloat && !isIntZero && text.startswith("0"))
449    Info.Octal = true;
450
451  SourceLocation B = literalRange.getBegin();
452  Info.WithoutSuffRange =
453      CharSourceRange::getCharRange(B, B.getLocWithOffset(text.size()));
454  return true;
455}
456
457static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg,
458                                   const NSAPI &NS, Commit &commit) {
459  if (Msg->getNumArgs() != 1)
460    return false;
461
462  const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts();
463  if (const CharacterLiteral *CharE = dyn_cast<CharacterLiteral>(Arg))
464    return rewriteToCharLiteral(Msg, CharE, NS, commit);
465  if (const ObjCBoolLiteralExpr *BE = dyn_cast<ObjCBoolLiteralExpr>(Arg))
466    return rewriteToBoolLiteral(Msg, BE, NS, commit);
467  if (const CXXBoolLiteralExpr *BE = dyn_cast<CXXBoolLiteralExpr>(Arg))
468    return rewriteToBoolLiteral(Msg, BE, NS, commit);
469
470  const Expr *literalE = Arg;
471  if (const UnaryOperator *UOE = dyn_cast<UnaryOperator>(literalE)) {
472    if (UOE->getOpcode() == UO_Plus || UOE->getOpcode() == UO_Minus)
473      literalE = UOE->getSubExpr();
474  }
475
476  // Only integer and floating literals; non-literals or imaginary literal
477  // cannot be rewritten.
478  if (!isa<IntegerLiteral>(literalE) && !isa<FloatingLiteral>(literalE))
479    return false;
480
481  ASTContext &Ctx = NS.getASTContext();
482  Selector Sel = Msg->getSelector();
483  llvm::Optional<NSAPI::NSNumberLiteralMethodKind>
484    MKOpt = NS.getNSNumberLiteralMethodKind(Sel);
485  if (!MKOpt)
486    return false;
487  NSAPI::NSNumberLiteralMethodKind MK = *MKOpt;
488
489  bool CallIsUnsigned = false, CallIsLong = false, CallIsLongLong = false;
490  bool CallIsFloating = false, CallIsDouble = false;
491
492  switch (MK) {
493  // We cannot have these calls with int/float literals.
494  case NSAPI::NSNumberWithChar:
495  case NSAPI::NSNumberWithUnsignedChar:
496  case NSAPI::NSNumberWithShort:
497  case NSAPI::NSNumberWithUnsignedShort:
498  case NSAPI::NSNumberWithBool:
499    return false;
500
501  case NSAPI::NSNumberWithUnsignedInt:
502  case NSAPI::NSNumberWithUnsignedInteger:
503    CallIsUnsigned = true;
504  case NSAPI::NSNumberWithInt:
505  case NSAPI::NSNumberWithInteger:
506    break;
507
508  case NSAPI::NSNumberWithUnsignedLong:
509    CallIsUnsigned = true;
510  case NSAPI::NSNumberWithLong:
511    CallIsLong = true;
512    break;
513
514  case NSAPI::NSNumberWithUnsignedLongLong:
515    CallIsUnsigned = true;
516  case NSAPI::NSNumberWithLongLong:
517    CallIsLongLong = true;
518    break;
519
520  case NSAPI::NSNumberWithDouble:
521    CallIsDouble = true;
522  case NSAPI::NSNumberWithFloat:
523    CallIsFloating = true;
524    break;
525  }
526
527  SourceRange ArgRange = Arg->getSourceRange();
528  QualType ArgTy = Arg->getType();
529  QualType CallTy = Msg->getArg(0)->getType();
530
531  // Check for the easy case, the literal maps directly to the call.
532  if (Ctx.hasSameType(ArgTy, CallTy)) {
533    commit.replaceWithInner(Msg->getSourceRange(), ArgRange);
534    commit.insert(ArgRange.getBegin(), "@");
535    return true;
536  }
537
538  // We will need to modify the literal suffix to get the same type as the call.
539  // Don't even try if it came from a macro.
540  if (ArgRange.getBegin().isMacroID())
541    return false;
542
543  bool LitIsFloat = ArgTy->isFloatingType();
544  // For a float passed to integer call, don't try rewriting. It is difficult
545  // and a very uncommon case anyway.
546  if (LitIsFloat && !CallIsFloating)
547    return false;
548
549  // Try to modify the literal make it the same type as the method call.
550  // -Modify the suffix, and/or
551  // -Change integer to float
552
553  LiteralInfo LitInfo;
554  bool isIntZero = false;
555  if (const IntegerLiteral *IntE = dyn_cast<IntegerLiteral>(literalE))
556    isIntZero = !IntE->getValue().getBoolValue();
557  if (!getLiteralInfo(ArgRange, LitIsFloat, isIntZero, Ctx, LitInfo))
558    return false;
559
560  // Not easy to do int -> float with hex/octal and uncommon anyway.
561  if (!LitIsFloat && CallIsFloating && (LitInfo.Hex || LitInfo.Octal))
562    return false;
563
564  SourceLocation LitB = LitInfo.WithoutSuffRange.getBegin();
565  SourceLocation LitE = LitInfo.WithoutSuffRange.getEnd();
566
567  commit.replaceWithInner(CharSourceRange::getTokenRange(Msg->getSourceRange()),
568                         LitInfo.WithoutSuffRange);
569  commit.insert(LitB, "@");
570
571  if (!LitIsFloat && CallIsFloating)
572    commit.insert(LitE, ".0");
573
574  if (CallIsFloating) {
575    if (!CallIsDouble)
576      commit.insert(LitE, LitInfo.F);
577  } else {
578    if (CallIsUnsigned)
579      commit.insert(LitE, LitInfo.U);
580
581    if (CallIsLong)
582      commit.insert(LitE, LitInfo.L);
583    else if (CallIsLongLong)
584      commit.insert(LitE, LitInfo.LL);
585  }
586  return true;
587}
588