diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index fcdabc69e0a..dc7729b4dc9 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -3957,9 +3957,6 @@ public: using namespace decls_block; verifyAttrSerializable(dtor); - if (S.allowCompilerErrors() && dtor->isInvalid()) - return; - auto contextID = S.addDeclContextRef(dtor->getDeclContext()); unsigned abbrCode = S.DeclTypeAbbrCodes[DestructorLayout::Code]; @@ -4001,12 +3998,34 @@ public: } }; +/// When allowing modules with errors there may be cases where there's little +/// point in serializing a declaration and doing so would create a maintenance +/// burden on the deserialization side. Returns \c true if the given declaration +/// should be skipped and \c false otherwise. +static bool canSkipWhenInvalid(const Decl *D) { + // There's no point writing out the deinit when its context is not a class + // as nothing would be able to reference it + if (auto *deinit = dyn_cast(D)) { + if (!isa(D->getDeclContext())) + return true; + } + return false; +} + void Serializer::writeASTBlockEntity(const Decl *D) { using namespace decls_block; PrettyStackTraceDecl trace("serializing", D); assert(DeclsToSerialize.hasRef(D)); + if (D->isInvalid()) { + assert(allowCompilerErrors() && + "cannot create a module with an invalid decl"); + + if (canSkipWhenInvalid(D)) + return; + } + BitOffset initialOffset = Out.GetCurrentBitNo(); SWIFT_DEFER { // This is important enough to leave on in Release builds. @@ -4016,8 +4035,6 @@ void Serializer::writeASTBlockEntity(const Decl *D) { } }; - assert((allowCompilerErrors() || !D->isInvalid()) && - "cannot create a module with an invalid decl"); if (isDeclXRef(D)) { writeCrossReference(D); return; diff --git a/test/Serialization/AllowErrors/invalid-deinit.swift b/test/Serialization/AllowErrors/invalid-deinit.swift new file mode 100644 index 00000000000..f49ea87c909 --- /dev/null +++ b/test/Serialization/AllowErrors/invalid-deinit.swift @@ -0,0 +1,21 @@ +// RUN: %empty-directory(%t) + +// Serialize and deserialize a deinit with SourceFile context to make sure we +// don't crash +// RUN: %target-swift-frontend -verify -module-name errors -emit-module -o %t/errors.swiftmodule -experimental-allow-module-with-compiler-errors %s +// RUN: %target-swift-ide-test -print-module -module-to-print=errors -source-filename=x -I %t -allow-compiler-errors + +// Also check it wasn't serialized +// RUN: llvm-bcanalyzer -dump %t/errors.swiftmodule | %FileCheck %s +// CHECK-NOT: DESTRUCTOR_DECL + +struct Foo {} + +@discardableResult // expected-error{{'@discardableResult' attribute cannot be applied to this declaration}} +deinit {} // expected-error{{deinitializers may only be declared within a class}} + +func foo() -> Foo { return Foo() } + +// Make sure @discardableResult isn't added to `foo`, which could be possible +// if the deinit is partially serialized +foo() // expected-warning{{result of call to 'foo()' is unused}} diff --git a/validation-test/Serialization/AllowErrors/invalid-deinit.swift b/validation-test/Serialization/AllowErrors/invalid-deinit.swift deleted file mode 100644 index 17dc6e06f4b..00000000000 --- a/validation-test/Serialization/AllowErrors/invalid-deinit.swift +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: %empty-directory(%t) - -// RUN: %target-swift-frontend -module-name errors -emit-module -o %t/errors.swiftmodule -experimental-allow-module-with-compiler-errors %s - -// deinit is only valid on a class, make sure we don't crash when allowing -// errors - -deinit {}