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<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
+ ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) ==
+ NullabilityKind::NonNull);
+}
+
+TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
+ Annotations Header(R"cpp(
+#pragma clang assume_nonnull begin // error-ok
+void foo(int *X);
+)cpp");
+ auto TU = TestTU::withCode(R"cpp(
+#include "foo.h" // unterminated assume_nonnull should not affect bar.
+void bar(int *Y);
+)cpp");
+ TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
+ auto AST = TU.build();
+ EXPECT_THAT(*AST.getDiagnostics(),
+ ElementsAre(diagName("pp_eof_in_assume_nonnull")));
+ const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
+ ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) ==
+ NullabilityKind::NonNull);
+ const auto *Y = cast<FunctionDecl>(findDecl(AST, "bar")).getParamDecl(0);
+ ASSERT_FALSE(
+ Y->getOriginalType()->getNullability(X->getASTContext()).hasValue());
+}
+
TEST(DiagnosticsTest, InsideMacros) {
Annotations Test(R"cpp(
#define TEN 10
/// \#pragma clang assume_nonnull begin.
SourceLocation PragmaAssumeNonNullLoc;
+ /// Set only for preambles which end with an active
+ /// \#pragma clang assume_nonnull begin.
+ ///
+ /// When the preamble is loaded into the main file,
+ /// `PragmaAssumeNonNullLoc` will be set to this to
+ /// replay the unterminated assume_nonnull.
+ SourceLocation PreambleRecordedPragmaAssumeNonNullLoc;
+
/// True if we hit the code-completion point.
bool CodeCompletionReached = false;
PragmaAssumeNonNullLoc = Loc;
}
+ /// Get the location of the recorded unterminated \#pragma clang
+ /// assume_nonnull begin in the preamble, if one exists.
+ ///
+ /// Returns an invalid location if the premable did not end with
+ /// such a pragma active or if there is no recorded preamble.
+ SourceLocation getPreambleRecordedPragmaAssumeNonNullLoc() const {
+ return PreambleRecordedPragmaAssumeNonNullLoc;
+ }
+
+ /// Record the location of the unterminated \#pragma clang
+ /// assume_nonnull begin in the preamble.
+ void setPreambleRecordedPragmaAssumeNonNullLoc(SourceLocation Loc) {
+ PreambleRecordedPragmaAssumeNonNullLoc = Loc;
+ }
+
/// Set the directory in which the main file should be considered
/// to have been found, if it is not a real file.
void setMainFileDir(const DirectoryEntry *Dir) {
///
/// 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.
/// Record code for included files.
PP_INCLUDED_FILES = 66,
+
+ /// Record code for an unterminated \#pragma clang assume_nonnull begin
+ /// recorded in a preamble.
+ PP_ASSUME_NONNULL_LOC = 67,
};
/// Record types used within a source manager block.
// instantiation or a _Pragma.
if (PragmaAssumeNonNullLoc.isValid() &&
!isEndOfMacro && !(CurLexer && CurLexer->Is_PragmaLexer)) {
- Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull);
-
+ // If we're at the end of generating a preamble, we should record the
+ // unterminated \#pragma clang assume_nonnull so we can restore it later
+ // when the preamble is loaded into the main file.
+ if (isRecordingPreamble() && isInPrimaryFile())
+ PreambleRecordedPragmaAssumeNonNullLoc = PragmaAssumeNonNullLoc;
+ else
+ Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull);
// Recover by leaving immediately.
PragmaAssumeNonNullLoc = SourceLocation();
}
PPCallbacks::ExitFile, FileType, ExitedFID);
}
- // Restore conditional stack from the preamble right after exiting from the
- // predefines file.
- if (ExitedFromPredefinesFile)
+ // Restore conditional stack as well as the recorded
+ // \#pragma clang assume_nonnull from the preamble right after exiting
+ // from the predefines file.
+ if (ExitedFromPredefinesFile) {
replayPreambleConditionalStack();
+ if (PreambleRecordedPragmaAssumeNonNullLoc.isValid())
+ PragmaAssumeNonNullLoc = PreambleRecordedPragmaAssumeNonNullLoc;
+ }
if (!isEndOfMacro && CurPPLexer && FoundPCHThroughHeader &&
(isInPrimaryFile() ||
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:
}
break;
+ case PP_ASSUME_NONNULL_LOC: {
+ unsigned Idx = 0;
+ if (!Record.empty())
+ PP.setPreambleRecordedPragmaAssumeNonNullLoc(
+ ReadSourceLocation(F, Record, Idx));
+ break;
+ }
+
case PP_CONDITIONAL_STACK:
if (!Record.empty()) {
unsigned Idx = 0, End = Record.size() - 1;
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);
Stream.EmitRecord(PP_COUNTER_VALUE, Record);
}
+ // If we have a recorded #pragma assume_nonnull, remember it so it can be
+ // replayed when the preamble terminates into the main file.
+ SourceLocation AssumeNonNullLoc =
+ PP.getPreambleRecordedPragmaAssumeNonNullLoc();
+ 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();
--- /dev/null
+// 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