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 @@ -745,6 +745,36 @@ EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1)); } +TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) { + Annotations Header(R"cpp( +__attribute__((objc_root_class)) +@interface BaseClass +- (id)description; +- (void)dump:(id)str; +@end +)cpp"); + auto TU = TestTU::withCode(R"cpp( +#include "header.h" + +#if defined(__has_attribute) && __has_feature(nullability) +#pragma clang assume_nonnull begin +#endif + +void printSomething(BaseClass *obj); + +void printSomething(BaseClass *obj) { + [obj dump:obj.description]; +} + +#if defined(__has_attribute) && __has_feature(nullability) +#pragma clang assume_nonnull end +#endif + )cpp"); + TU.Filename = "foo.m"; + TU.AdditionalFiles["header.h"] = std::string(Header.code()); + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); +} + 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. In addition, for preambles, we keep track + /// of 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,13 @@ PragmaARCCFCodeAuditedInfo = {nullptr, SourceLocation()}; } - // Complain about reaching a true EOF within assume_nonnull. + // Complain about reaching a true EOF within assume_nonnull unless we're + // building a preamble. + // // We don't want to complain about reaching the end of a macro // instantiation or a _Pragma. - if (PragmaAssumeNonNullLoc.isValid() && + 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,15 @@ Stream.EmitRecord(PP_COUNTER_VALUE, Record); } + // If we have an unterminated pragma assume non null, remember it since it + // might be at the end of the preamble. + SourceLocation AssumeNonNullLoc = PP.getPragmaAssumeNonNullLoc(); + if (AssumeNonNullLoc.isValid()) { + AddSourceLocation(AssumeNonNullLoc, Record); + Stream.EmitRecord(PP_ASSUME_NONNULL_LOC, Record); + Record.clear(); + } + if (PP.isRecordingPreamble() && PP.hasRecordedPreamble()) { assert(!IsModule); auto SkipInfo = PP.getPreambleSkipInfo();