Skip to content

Serialization: Fix deserializing opaque types details for computed properties and subscripts #69546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3324,6 +3324,9 @@ class OpaqueTypeDecl final :
};
}

/// Should the underlying type be visible to clients outside of the module?
bool exportUnderlyingType() const;

/// The substitutions that map the generic parameters of the opaque type to
/// the unique underlying types, when that information is known.
llvm::Optional<SubstitutionMap> getUniqueUnderlyingTypeSubstitutions() const {
Expand Down
19 changes: 19 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9547,6 +9547,25 @@ GenericTypeParamDecl *OpaqueTypeDecl::getExplicitGenericParam(
return genericParamType->getDecl();
}

bool OpaqueTypeDecl::exportUnderlyingType() const {
auto mod = getDeclContext()->getParentModule();
if (mod->getResilienceStrategy() != ResilienceStrategy::Resilient)
return true;

ValueDecl *namingDecl = getNamingDecl();
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(namingDecl))
return AFD->getResilienceExpansion() == ResilienceExpansion::Minimal;

if (auto *ASD = dyn_cast<AbstractStorageDecl>(namingDecl)) {
for (auto *accessor : ASD->getAllAccessors())
if (accessor->getResilienceExpansion() == ResilienceExpansion::Minimal)
return true;
return false;
}

llvm_unreachable("The naming decl is expected to be either an AFD or ASD");
}

llvm::Optional<unsigned>
OpaqueTypeDecl::getAnonymousOpaqueParamOrdinal(TypeRepr *repr) const {
assert(NamingDeclAndHasOpaqueReturnTypeRepr.getInt() &&
Expand Down
21 changes: 16 additions & 5 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4221,11 +4221,13 @@ class DeclDeserializer {
GenericSignatureID genericSigID;
SubstitutionMapID underlyingTypeSubsID;
uint8_t rawAccessLevel;
bool exportUnderlyingType;
decls_block::OpaqueTypeLayout::readRecord(scratch, contextID,
namingDeclID, interfaceSigID,
interfaceTypeID, genericSigID,
underlyingTypeSubsID,
rawAccessLevel);
rawAccessLevel,
exportUnderlyingType);

auto declContext = MF.getDeclContext(contextID);
auto interfaceSigOrErr = MF.getGenericSignatureChecked(interfaceSigID);
Expand Down Expand Up @@ -4261,16 +4263,25 @@ class DeclDeserializer {
else
opaqueDecl->setGenericSignature(GenericSignature());

auto *AFD = dyn_cast<AbstractFunctionDecl>(namingDecl);
if (MF.getResilienceStrategy() == ResilienceStrategy::Resilient &&
!MF.FileContext->getParentModule()->isMainModule() &&
AFD && AFD->getResilienceExpansion() != ResilienceExpansion::Minimal) {
if (!MF.FileContext->getParentModule()->isMainModule() &&
!exportUnderlyingType) {
// Do not try to read the underlying type information if the function
// is not inlinable in clients. This reflects the swiftinterface behavior
// in where clients are only aware of the underlying type when the body
// of the function is public.
LLVM_DEBUG(
llvm::dbgs() << "Ignoring underlying information for opaque type of '";
llvm::dbgs() << namingDecl->getName();
llvm::dbgs() << "'\n";
);

} else if (underlyingTypeSubsID) {
LLVM_DEBUG(
llvm::dbgs() << "Loading underlying information for opaque type of '";
llvm::dbgs() << namingDecl->getName();
llvm::dbgs() << "'\n";
);

auto subMapOrError = MF.getSubstitutionMapChecked(underlyingTypeSubsID);
if (!subMapOrError) {
// If the underlying type references internal details, ignore it.
Expand Down
5 changes: 3 additions & 2 deletions lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 816; // throw_addr
const uint16_t SWIFTMODULE_VERSION_MINOR = 817; // Opaque type export details

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down Expand Up @@ -1641,7 +1641,8 @@ namespace decls_block {
TypeIDField, // interface type for opaque type
GenericSignatureIDField, // generic environment
SubstitutionMapIDField, // optional substitution map for underlying type
AccessLevelField // access level
AccessLevelField, // access level
BCFixed<1> // export underlying type details
// trailed by generic parameters
// trailed by conditional substitutions
>;
Expand Down
5 changes: 4 additions & 1 deletion lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4490,11 +4490,14 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
}
uint8_t rawAccessLevel =
getRawStableAccessLevel(opaqueDecl->getFormalAccess());
bool exportDetails = opaqueDecl->exportUnderlyingType();

unsigned abbrCode = S.DeclTypeAbbrCodes[OpaqueTypeLayout::Code];
OpaqueTypeLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
contextID.getOpaqueValue(), namingDeclID,
interfaceSigID, interfaceTypeID, genericSigID,
underlyingSubsID, rawAccessLevel);
underlyingSubsID, rawAccessLevel,
exportDetails);
writeGenericParams(opaqueDecl->getGenericParams());

// Serialize all of the conditionally available substitutions expect the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/// Variant of ignore-opaque-underlying-type because of the macOS host need.
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// REQUIRES: asserts
// REQUIRES: OS=macosx

/// Resilient scenario, we ignore underlying type of non-inlinable functions.
/// Build libraries.
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -emit-module-path %t/Lib.swiftmodule \
// RUN: -emit-module-interface-path %t/Lib.swiftinterface -verify

/// Build clients, with and without safety.
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -enable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-access-control 2>&1 \
// RUN: | %FileCheck %s

/// Build against the swiftinterface.
// RUN: rm %t/Lib.swiftmodule
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

/// Non-resilient scenario, all underlying types are loaded.
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
// RUN: -swift-version 5 \
// RUN: -emit-module-path %t/Lib.swiftmodule -verify
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck --check-prefix=NON-RESILIENT %s
// NON-RESILIENT-NOT: Ignoring underlying information

//--- Lib.swift
public protocol V {}

public struct EV : V {
public init () {}
}

@available(SwiftStdlib 5.1, *)
public extension V {
// CHECK: Loading underlying information for opaque type of 'backdeployedOpaqueFunc()'
@backDeployed(before: SwiftStdlib 5.1) // expected-warning 4 {{'@backDeployed' is unsupported on a instance method with a 'some' return type}}
func backdeployedOpaqueFunc() -> some V { EV() }
}

//--- Client.swift
import Lib

if #available(SwiftStdlib 5.1, *) {
let v = EV()
let _ = v.backdeployedOpaqueFunc()
}
122 changes: 122 additions & 0 deletions test/Serialization/ignore-opaque-underlying-type.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// REQUIRES: asserts

/// Resilient scenario, we ignore underlying type of non-inlinable functions.
/// Build libraries.
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -emit-module-path %t/Lib.swiftmodule \
// RUN: -emit-module-interface-path %t/Lib.swiftinterface -verify

/// Build clients, with and without safety.
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -enable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-access-control 2>&1 \
// RUN: | %FileCheck %s

/// Build against the swiftinterface.
// RUN: rm %t/Lib.swiftmodule
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck %s

/// Non-resilient scenario, all underlying types are loaded.
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
// RUN: -swift-version 5 \
// RUN: -emit-module-path %t/Lib.swiftmodule -verify
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -verify -Xllvm -debug-only=Serialization \
// RUN: -disable-deserialization-safety 2>&1 \
// RUN: | %FileCheck --check-prefix=NON-RESILIENT %s
// NON-RESILIENT-NOT: Ignoring underlying information

//--- Lib.swift
public protocol V {}

public struct EV : V {
public init () {}
}

@available(SwiftStdlib 5.1, *)
public extension V {
private func referencedPrivateFunc(v: some V) -> some V { return v }

/// Hidden underlying types.
// CHECK: Ignoring underlying information for opaque type of 'opaqueReferencingPrivate()'
func opaqueReferencingPrivate() -> some V {
referencedPrivateFunc(v: EV())
}

// CHECK: Ignoring underlying information for opaque type of 'opaqueReferencingPrivateVar'
var opaqueReferencingPrivateVar: some V {
referencedPrivateFunc(v: EV())
}

// CHECK: Ignoring underlying information for opaque type of 'opaqueReferencingPrivateVarPattern'
var opaqueReferencingPrivateVarPattern: some V {
get {
referencedPrivateFunc(v: EV())
}
}

// CHECK: Ignoring underlying information for opaque type of 'subscript(_:)'
subscript(v: some V) -> some V {
referencedPrivateFunc(v: v)
}

/// Visible underlying types.
// CHECK: Loading underlying information for opaque type of 'inlinableOpaqueFunc()'
@inlinable
func inlinableOpaqueFunc() -> some V { EV() }

// CHECK: Loading underlying information for opaque type of 'aeicOpaqueFunc()'
@_alwaysEmitIntoClient
func aeicOpaqueFunc() -> some V { EV() }

// CHECK: Loading underlying information for opaque type of 'transparentOpaqueFunc()'
@_transparent
func transparentOpaqueFunc() -> some V { EV() }

// CHECK: Loading underlying information for opaque type of 'inlinableOpaqueVar'
@inlinable
var inlinableOpaqueVar: some V { EV() }

// CHECK: Loading underlying information for opaque type of 'inlinableOpaqueVarPattern'
var inlinableOpaqueVarPattern: some V {
@inlinable
get { EV() }
}
}

//--- Client.swift
import Lib

if #available(SwiftStdlib 5.1, *) {
let v = EV()
let _ = v.opaqueReferencingPrivate()
let _ = v.opaqueReferencingPrivateVar
let _ = v.opaqueReferencingPrivateVarPattern
let _ = v[v]

let _ = v.inlinableOpaqueFunc()
let _ = v.aeicOpaqueFunc()
let _ = v.transparentOpaqueFunc()

let _ = v.inlinableOpaqueVar
let _ = v.inlinableOpaqueVarPattern
}