diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -20,6 +20,7 @@ #include "index/MemIndex.h" #include "support/Context.h" #include "support/Path.h" +#include "clang/AST/Attr.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticSema.h" #include "llvm/Support/ScopedPrinter.h" @@ -745,6 +746,19 @@ EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1)); } +TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) { + auto TU = TestTU::withCode(R"cpp( +#pragma clang assume_nonnull begin +void foo(int *x); +#pragma clang assume_nonnull end +)cpp"); + auto AST = TU.build(); + EXPECT_THAT(*AST.getDiagnostics(), IsEmpty()); + const auto *X = cast(findDecl(AST, "foo")).getParamDecl(0); + ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) == + NullabilityKind::NonNull); +} + TEST(DiagnosticsTest, InsideMacros) { Annotations Test(R"cpp( #define TEN 10 diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h --- a/clang/include/clang/Lex/PreprocessorOptions.h +++ b/clang/include/clang/Lex/PreprocessorOptions.h @@ -128,7 +128,8 @@ /// /// When the lexer is done, one of the things that need to be preserved is the /// conditional #if stack, so the ASTWriter/ASTReader can save/restore it when - /// processing the rest of the file. + /// processing the rest of the file. Similarly, we track an unterminated + /// #pragma assume_nonnull. bool GeneratePreamble = false; /// Whether to write comment locations into the PCH when building it. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -698,6 +698,9 @@ /// Record code for included files. PP_INCLUDED_FILES = 66, + + /// Record code for an unterminated \#pragma clang assume_nonnull begin. + PP_ASSUME_NONNULL_LOC = 67, }; /// Record types used within a source manager block. diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -427,10 +427,16 @@ PragmaARCCFCodeAuditedInfo = {nullptr, SourceLocation()}; } - // Complain about reaching a true EOF within assume_nonnull. + // Complain about reaching a true EOF. + // // We don't want to complain about reaching the end of a macro // instantiation or a _Pragma. - if (PragmaAssumeNonNullLoc.isValid() && + // + // In addition, we don't want to complain about reaching the end of the + // preamble that we're generating, nor the end of the preamble that the main + // file is using. + if (PragmaAssumeNonNullLoc.isValid() && !this->PPOpts->GeneratePreamble && + !(CurLexer && CurLexer->getFileID() == PredefinesFileID) && !isEndOfMacro && !(CurLexer && CurLexer->Is_PragmaLexer)) { Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull); diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3109,6 +3109,7 @@ case IDENTIFIER_OFFSET: case INTERESTING_IDENTIFIERS: case STATISTICS: + case PP_ASSUME_NONNULL_LOC: case PP_CONDITIONAL_STACK: case PP_COUNTER_VALUE: case SOURCE_LOCATION_OFFSETS: @@ -3371,6 +3372,13 @@ } break; + case PP_ASSUME_NONNULL_LOC: { + unsigned Idx = 0; + if (!Record.empty()) + PP.setPragmaAssumeNonNullLoc(ReadSourceLocation(F, Record, Idx)); + break; + } + case PP_CONDITIONAL_STACK: if (!Record.empty()) { unsigned Idx = 0, End = Record.size() - 1; diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -872,6 +872,7 @@ RECORD(PP_CONDITIONAL_STACK); RECORD(DECLS_TO_CHECK_FOR_DEFERRED_DIAGS); RECORD(PP_INCLUDED_FILES); + RECORD(PP_ASSUME_NONNULL_LOC); // SourceManager Block. BLOCK(SOURCE_MANAGER_BLOCK); @@ -2299,6 +2300,16 @@ Stream.EmitRecord(PP_COUNTER_VALUE, Record); } + // If we have an unterminated #pragma assume_nonnull, remember it since we + // should be at the end of a preamble. + SourceLocation AssumeNonNullLoc = PP.getPragmaAssumeNonNullLoc(); + if (AssumeNonNullLoc.isValid()) { + assert(PP.isRecordingPreamble()); + AddSourceLocation(AssumeNonNullLoc, Record); + Stream.EmitRecord(PP_ASSUME_NONNULL_LOC, Record); + Record.clear(); + } + if (PP.isRecordingPreamble() && PP.hasRecordedPreamble()) { assert(!IsModule); auto SkipInfo = PP.getPreambleSkipInfo(); diff --git a/clang/test/Index/preamble-assume-nonnull.c b/clang/test/Index/preamble-assume-nonnull.c new file mode 100644 --- /dev/null +++ b/clang/test/Index/preamble-assume-nonnull.c @@ -0,0 +1,6 @@ +// RUN: env CINDEXTEST_EDITING=1 c-index-test -test-load-source local %s 2>&1 \ +// RUN: | FileCheck %s --implicit-check-not "error:" + +#pragma clang assume_nonnull begin +void foo(int *x); +#pragma clang assume_nonnull end