ExprTy = S.Context.CharTy;
}
+ // %C in an Objective-C context prints a unichar, not a wchar_t.
+ // If the argument is an integer of some kind, believe the %C and suggest
+ // a cast instead of changing the conversion specifier.
QualType IntendedTy = ExprTy;
+ if (ObjCContext &&
+ FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
+ if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
+ !ExprTy->isCharType()) {
+ // 'unichar' is defined as a typedef of unsigned short, but we should
+ // prefer using the typedef if it is visible.
+ IntendedTy = S.Context.UnsignedShortTy;
+
+ LookupResult Result(S, &S.Context.Idents.get("unichar"), E->getLocStart(),
+ Sema::LookupOrdinaryName);
+ if (S.LookupName(Result, S.getCurScope())) {
+ NamedDecl *ND = Result.getFoundDecl();
+ if (TypedefNameDecl *TD = dyn_cast<TypedefNameDecl>(ND))
+ if (TD->getUnderlyingType() == IntendedTy)
+ IntendedTy = S.Context.getTypedefType(TD);
+ }
+ }
+ }
+
+ // Special-case some of Darwin's platform-independence types by suggesting
+ // casts to primitive types that are known to be large enough.
+ bool ShouldNotPrintDirectly = false;
if (S.Context.getTargetInfo().getTriple().isOSDarwin()) {
- // Special-case some of Darwin's platform-independence types.
if (const TypedefType *UserTy = IntendedTy->getAs<TypedefType>()) {
StringRef Name = UserTy->getDecl()->getName();
- IntendedTy = llvm::StringSwitch<QualType>(Name)
+ QualType CastTy = llvm::StringSwitch<QualType>(Name)
.Case("NSInteger", S.Context.LongTy)
.Case("NSUInteger", S.Context.UnsignedLongTy)
.Case("SInt32", S.Context.IntTy)
.Case("UInt32", S.Context.UnsignedIntTy)
- .Default(IntendedTy);
+ .Default(QualType());
+
+ if (!CastTy.isNull()) {
+ ShouldNotPrintDirectly = true;
+ IntendedTy = CastTy;
+ }
}
}
CharSourceRange SpecRange = getSpecifierRange(StartSpecifier, SpecifierLen);
- if (IntendedTy != ExprTy) {
+ if (IntendedTy == ExprTy) {
+ // In this case, the specifier is wrong and should be changed to match
+ // the argument.
+ EmitFormatDiagnostic(
+ S.PDiag(diag::warn_printf_conversion_argument_type_mismatch)
+ << AT.getRepresentativeTypeName(S.Context) << IntendedTy
+ << E->getSourceRange(),
+ E->getLocStart(),
+ /*IsStringLocation*/false,
+ SpecRange,
+ FixItHint::CreateReplacement(SpecRange, os.str()));
+
+ } else {
// The canonical type for formatting this value is different from the
// actual type of the expression. (This occurs, for example, with Darwin's
// NSInteger on 32-bit platforms, where it is typedef'd as 'int', but
Hints.push_back(FixItHint::CreateInsertion(After, ")"));
}
- // We extract the name from the typedef because we don't want to show
- // the underlying type in the diagnostic.
- const TypedefType *UserTy = cast<TypedefType>(ExprTy);
- StringRef Name = UserTy->getDecl()->getName();
-
- // Finally, emit the diagnostic.
- EmitFormatDiagnostic(S.PDiag(diag::warn_format_argument_needs_cast)
- << Name << IntendedTy
- << E->getSourceRange(),
- E->getLocStart(), /*IsStringLocation=*/false,
- SpecRange, Hints);
- } else {
- EmitFormatDiagnostic(
- S.PDiag(diag::warn_printf_conversion_argument_type_mismatch)
- << AT.getRepresentativeTypeName(S.Context) << IntendedTy
- << E->getSourceRange(),
- E->getLocStart(),
- /*IsStringLocation*/false,
- SpecRange,
- FixItHint::CreateReplacement(SpecRange, os.str()));
+ if (ShouldNotPrintDirectly) {
+ // The expression has a type that should not be printed directly.
+ // We extract the name from the typedef because we don't want to show
+ // the underlying type in the diagnostic.
+ StringRef Name = cast<TypedefType>(ExprTy)->getDecl()->getName();
+
+ EmitFormatDiagnostic(S.PDiag(diag::warn_format_argument_needs_cast)
+ << Name << IntendedTy
+ << E->getSourceRange(),
+ E->getLocStart(), /*IsStringLocation=*/false,
+ SpecRange, Hints);
+ } else {
+ // In this case, the expression could be printed using a different
+ // specifier, but we've decided that the specifier is probably correct
+ // and we should cast instead. Just use the normal warning message.
+ EmitFormatDiagnostic(
+ S.PDiag(diag::warn_printf_conversion_argument_type_mismatch)
+ << AT.getRepresentativeTypeName(S.Context) << ExprTy
+ << E->getSourceRange(),
+ E->getLocStart(), /*IsStringLocation*/false,
+ SpecRange, Hints);
+ }
}
} else {
const CharSourceRange &CSR = getSpecifierRange(StartSpecifier,
NSLog(@"%c", 'abcd'); // missing-warning{{format specifies type 'char' but the argument has type 'int'}}
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
}
+
+
+void test_percent_C() {
+ const unsigned short data = 'a';
+ NSLog(@"%C", data); // no-warning
+
+ NSLog(@"%C", 0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unsigned short)"
+
+ typedef unsigned short unichar;
+
+ NSLog(@"%C", 0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
+
+ NSLog(@"%C", data ? 0x2F : 0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)("
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:36-[[@LINE-3]]:36}:")"
+
+ NSLog(@"%C", 0.0); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'double'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%f"
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
+
+ NSLog(@"%C", (char)0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'char'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:22}:"(unichar)"
+
+ NSLog(@"%C", 'a'); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'char'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:22}:"(unichar)"
+}
--- /dev/null
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// RUN: %clang_cc1 -fdiagnostics-parseable-fixits -fblocks %s 2>&1 | FileCheck %s
+
+extern "C" void NSLog(id, ...);
+
+void test_percent_C() {
+ const unsigned short data = 'a';
+ NSLog(@"%C", data); // no-warning
+
+ const wchar_t wchar_data = L'a';
+ NSLog(@"%C", wchar_data); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'wchar_t'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"(unsigned short)"
+
+ NSLog(@"%C", 0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unsigned short)"
+
+ typedef unsigned short unichar;
+
+ NSLog(@"%C", wchar_data); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'wchar_t'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"(unichar)"
+
+ NSLog(@"%C", 0x2603); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
+
+ NSLog(@"%C", 0.0); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'double'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%f"
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
+}
NSLog(@"%S", ptr); // no-warning
const wchar_t* wchar_ptr = L"ab";
- NSLog(@"%S", wchar_ptr); // expected-warning{{format specifies type 'const unsigned short *' but the argument has type 'const wchar_t *'}}
+ NSLog(@"%S", wchar_ptr); // expected-warning{{format specifies type 'const unichar *' (aka 'const unsigned short *') but the argument has type 'const wchar_t *'}}
}
void test_percent_ls() {
NSLog(@"%ls", ptr); // no-warning
const wchar_t* wchar_ptr = L"ab";
- NSLog(@"%ls", wchar_ptr); // expected-warning{{format specifies type 'const unsigned short *' but the argument has type 'const wchar_t *'}}
+ NSLog(@"%ls", wchar_ptr); // expected-warning{{format specifies type 'const unichar *' (aka 'const unsigned short *') but the argument has type 'const wchar_t *'}}
}
void test_percent_C() {
NSLog(@"%C", data); // no-warning
const wchar_t wchar_data = L'a';
- NSLog(@"%C", wchar_data); // expected-warning{{format specifies type 'unsigned short' but the argument has type 'wchar_t'}}
+ NSLog(@"%C", wchar_data); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'wchar_t'}}
}
// Test that %@ works with toll-free bridging (<rdar://problem/10814120>).