diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -531,6 +531,10 @@ const LangOptions &LangOpts, bool SkipTrailingWhitespaceAndNewLine); + /// Returns true if the token beginning at this Loc is `const`. + static bool isConst(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts); + /// Returns true if the given character could appear in an identifier. static bool isIdentifierBodyChar(char c, const LangOptions &LangOpts); diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -1299,6 +1299,28 @@ return TokenLoc.getLocWithOffset(Tok->getLength() + NumWhitespaceChars); } +bool Lexer::isConst(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts) { + std::pair LocInfo = SM.getDecomposedLoc(Loc); + if (LocInfo.first.isInvalid()) + return false; + + bool Invalid = false; + StringRef Buffer = SM.getBufferData(LocInfo.first, &Invalid); + if (Invalid) + return false; + + if (LocInfo.second > Buffer.size()) + return false; + + const char *LexStart = Buffer.data() + LocInfo.second; + StringRef StartTok(LexStart, Buffer.size() - LocInfo.second); + + return StartTok.consume_front("const") && + (StartTok.empty() || isWhitespace(StartTok[0]) || + StartTok.startswith("/*") || StartTok.startswith("//")); +} + /// getCharAndSizeSlow - Peek a single 'character' from the specified buffer, /// get its size, and return it. This is tricky in several cases: /// 1. If currently at the start of a trigraph, we warn about the trigraph, diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -14243,11 +14243,24 @@ : FixItHint{}); } } else { + // If the return type has `const` qualifier, we want to insert `static` + // before `const` (and not before the typename). + auto findBeginLoc = [&]() { + if ((FD->getReturnType()->isAnyPointerType() && + FD->getReturnType()->getPointeeType().isConstQualified()) || + FD->getReturnType().isConstQualified()) + // But only do this if we can determine where the `const` is. + if (clang::Lexer::isConst(FD->getBeginLoc(), getSourceManager(), + getLangOpts())) + + return FD->getBeginLoc(); + + return FD->getTypeSpecStartLoc(); + }; Diag(FD->getTypeSpecStartLoc(), diag::note_static_for_internal_linkage) << /* function */ 1 << (FD->getStorageClass() == SC_None - ? FixItHint::CreateInsertion(FD->getTypeSpecStartLoc(), - "static ") + ? FixItHint::CreateInsertion(findBeginLoc(), "static ") : FixItHint{}); } diff --git a/clang/test/Sema/warn-missing-prototypes.c b/clang/test/Sema/warn-missing-prototypes.c --- a/clang/test/Sema/warn-missing-prototypes.c +++ b/clang/test/Sema/warn-missing-prototypes.c @@ -49,3 +49,57 @@ void not_a_prototype_test(); // expected-note{{this declaration is not a prototype; add 'void' to make it a prototype for a zero-parameter function}} // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:27-[[@LINE-1]]:27}:"void" void not_a_prototype_test() { } // expected-warning{{no previous prototype for function 'not_a_prototype_test'}} + +const int *get_const() { // expected-warning{{no previous prototype for function 'get_const'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:1-[[@LINE-2]]:1}:"static " + int *ret; + return ret; +} + +struct MyStruct {}; + +const struct MyStruct get_struct() { // expected-warning{{no previous prototype for function 'get_struct'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:1-[[@LINE-2]]:1}:"static " + struct MyStruct ret; + return ret; +} + +// Turn off clang-format for white-space dependent testing. +// clang-format off +// Two spaces between cost and struct +const struct MyStruct get_struct_2() { // expected-warning{{no previous prototype for function 'get_struct_2'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:1-[[@LINE-2]]:1}:"static " + struct MyStruct ret; + return ret; +} + +// Random comment before const. We should put the `static` between the comment block and `const` +/*some randome comment*/const struct MyStruct get_struct_3() { // expected-warning{{no previous prototype for function 'get_struct_3'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:25-[[@LINE-2]]:25}:"static " + struct MyStruct ret; + return ret; +} + +// Random comment after const. +const/*some randome comment*/ struct MyStruct get_struct_4() { // expected-warning{{no previous prototype for function 'get_struct_4'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:1-[[@LINE-2]]:1}:"static " + struct MyStruct ret; + return ret; +} +// clang-format on + +#define MY_CONST const + +// This is not supported. We can't expand MY_CONST (efficiently) so we just give +// up and put the 'static' before 'struct' +MY_CONST struct MyStruct get_struct_nyi() { // expected-warning{{no previous prototype for function 'get_struct_nyi'}} + // expected-note@-1{{declare 'static' if the function is not intended to be used outside of this translation unit}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:10-[[@LINE-2]]:10}:"static " + struct MyStruct ret; + return ret; +}