Skip to content

[AST] Special handling for existentials with superclass and marker pr… #71855

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 4 commits into from
Mar 8, 2024
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
4 changes: 4 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -6050,6 +6050,10 @@ class ProtocolCompositionType final : public TypeBase,
return Bits.ProtocolCompositionType.HasExplicitAnyObject;
}

/// Produce a new type (potentially not be a protoocl composition)
/// which drops all of the marker protocol types associated with this one.
Type withoutMarkerProtocols() const;

// Implement isa/cast/dyncast/etc.
static bool classof(const TypeBase *T) {
return T->getKind() == TypeKind::ProtocolComposition;
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTMangler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,13 @@ void ASTMangler::appendType(Type type, GenericSignature sig,

case TypeKind::ProtocolComposition: {
auto *PCT = cast<ProtocolCompositionType>(tybase);

if (!AllowMarkerProtocols) {
auto strippedTy = PCT->withoutMarkerProtocols();
if (!strippedTy->isEqual(PCT))
return appendType(strippedTy, sig, forDecl);
}

if (PCT->hasParameterizedExistential()
|| (PCT->hasInverse() && AllowInverses))
return appendConstrainedExistential(PCT, sig, forDecl);
Expand Down
55 changes: 40 additions & 15 deletions lib/AST/ConformanceLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,46 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {

assert(type->isExistentialType());

auto getConstraintType = [&type]() {
if (auto *existentialTy = type->getAs<ExistentialType>())
return existentialTy->getConstraintType();
return type;
};

auto lookupSuperclassConformance = [&](ExistentialLayout &layout) {
if (auto superclass = layout.explicitSuperclass) {
if (auto result =
lookupConformance(superclass, protocol, /*allowMissing=*/false)) {
if (protocol->isSpecificProtocol(KnownProtocolKind::Sendable) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the true branch reachable? It seems we only call this if !protocol->existentialConformsToSelf() but that would be false for Sendable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved this code over so I'm not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this in a couple other places I think, will investigate!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we do call this in two places where !protocol->existentialConformsToSelf() and later on in case when it is, so it would be reachable at leave in one spot.

result.hasUnavailableConformance())
return ProtocolConformanceRef::forInvalid();
return result;
}
}
return ProtocolConformanceRef::forInvalid();
};

// If the existential type cannot be represented or the protocol does not
// conform to itself, there's no point in looking further.
if (!protocol->existentialConformsToSelf())
if (!protocol->existentialConformsToSelf()) {
// If type is a protocol composition with marker protocols
// check whether superclass conforms, and if it does form
// an inherited conformance. This means that types like:
// `KeyPath<String, Int> & Sendable` don't have to be "opened"
// to satisfy conformance to i.e. `Equatable`.
if (getConstraintType()->is<ProtocolCompositionType>()) {
auto layout = type->getExistentialLayout();
if (llvm::all_of(layout.getProtocols(),
[](const auto *P) { return P->isMarkerProtocol(); })) {
if (auto conformance = lookupSuperclassConformance(layout)) {
return ProtocolConformanceRef(
ctx.getInheritedConformance(type, conformance.getConcrete()));
}
}
}

return ProtocolConformanceRef::forInvalid();
}

if (protocol->isSpecificProtocol(KnownProtocolKind::Copyable)
&& !ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics)) {
Expand All @@ -89,10 +125,7 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
// existential to an archetype parameter, so for now we restrict this to
// @objc protocols and marker protocols.
if (!layout.isObjC() && !protocol->isMarkerProtocol()) {
auto constraint = type;
if (auto existential = constraint->getAs<ExistentialType>())
constraint = existential->getConstraintType();

auto constraint = getConstraintType();
// There's a specific exception for protocols with self-conforming
// witness tables, but the existential has to be *exactly* that type.
// TODO: synthesize witness tables on-demand for protocol compositions
Expand All @@ -107,16 +140,8 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {

// If the existential is class-constrained, the class might conform
// concretely.
if (auto superclass = layout.explicitSuperclass) {
if (auto result = lookupConformance(
superclass, protocol, /*allowMissing=*/false)) {
if (protocol->isSpecificProtocol(KnownProtocolKind::Sendable) &&
result.hasUnavailableConformance())
result = ProtocolConformanceRef::forInvalid();

return result;
}
}
if (auto conformance = lookupSuperclassConformance(layout))
return conformance;

// Otherwise, the existential might conform abstractly.
for (auto protoDecl : layout.getProtocols()) {
Expand Down
14 changes: 14 additions & 0 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3704,6 +3704,20 @@ Type ProtocolCompositionType::getInverseOf(const ASTContext &C,
/*HasExplicitAnyObject=*/false);
}

Type ProtocolCompositionType::withoutMarkerProtocols() const {
SmallVector<Type, 4> newMembers;
llvm::copy_if(getMembers(), std::back_inserter(newMembers), [](Type member) {
auto *P = member->getAs<ProtocolType>();
return !(P && P->getDecl()->isMarkerProtocol());
});

if (newMembers.size() == getMembers().size())
return Type(const_cast<ProtocolCompositionType *>(this));

return ProtocolCompositionType::get(getASTContext(), newMembers,
getInverses(), hasExplicitAnyObject());
}

Type ProtocolCompositionType::get(const ASTContext &C,
ArrayRef<Type> Members,
InvertibleProtocolSet Inverses,
Expand Down
3 changes: 3 additions & 0 deletions lib/IRGen/IRGenMangler.h
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@ class IRGenMangler : public Mangle::ASTMangler {
llvm::function_ref<void ()> body);

std::string mangleTypeSymbol(Type type, const char *Op) {
llvm::SaveAndRestore<bool> savedAllowMarkerProtocols(AllowMarkerProtocols,
false);

beginMangling();
appendType(type, nullptr);
appendOperator(Op);
Expand Down
31 changes: 31 additions & 0 deletions test/Constraints/protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,34 @@ _ = P_61517(false) // expected-error{{type 'any P_61517' cannot be instantiated}

_ = P_61517.init() // expected-error{{type 'any P_61517' cannot be instantiated}}
_ = P_61517.init(false) // expected-error{{type 'any P_61517' cannot be instantiated}}

@_marker
protocol Marker {}

do {
class C : Fooable, Error {
func foo() {}
}

struct Other {}

func overloaded() -> C & Marker {
fatalError()
}

func overloaded() -> Other {
fatalError()
}

func isFooable<T: Fooable>(_: T) {}

isFooable(overloaded()) // Ok

func isError<T: Error>(_: T) {}

isError(overloaded()) // Ok

func isFooableError<T: Fooable & Error>(_: T) {}

isFooableError(overloaded()) // Ok
}
16 changes: 14 additions & 2 deletions test/IRGen/marker_protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension Int: P { }
extension Array: P where Element: P { }

// No mention of the marker protocol for runtime type instantiation.
// CHECK-LABEL: @"$sSS_15marker_protocol1P_ptMD" =
// CHECK-LABEL: @"$sSS_yptMD" =
// CHECK-SAME: @"symbolic SS_ypt"

// CHECK-LABEL: @"$s15marker_protocol1QMp" = {{(dllexport |protected )?}}constant
Expand Down Expand Up @@ -47,7 +47,7 @@ struct HasMarkers {

// Note: no mention of marker protocols when forming a dictionary.
// CHECK-LABEL: define{{.*}}@"$s15marker_protocol0A12InDictionaryypyF"
// CHECK: call ptr @__swift_instantiateConcreteTypeFromMangledName({{.*}} @"$sSS_15marker_protocol1P_ptMD")
// CHECK: call ptr @__swift_instantiateConcreteTypeFromMangledName({{.*}} @"$sSS_yptMD")
public func markerInDictionary() -> Any {
let dict: [String: P] = ["answer" : 42]
return dict
Expand Down Expand Up @@ -85,3 +85,15 @@ protocol P2 {

// CHECK: define{{.*}}$s15marker_protocol3fooyy3FooQz_xtAA1PRzAA2P2RzlF
func foo<T: P & P2>(_: T.Foo, _: T) { }

class C {}

let v1 = (any C & P).self
let v2 = C.self

// CHECK-LABEL: define hidden swiftcc void @"$s15marker_protocol23testProtocolCompositionyyF"()
// CHECK: [[V1:%.*]] = call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s15marker_protocol1CCMD")
// CHECK: [[V2:%.*]] = load ptr, ptr @"$s15marker_protocol2v2AA1CCmvp"
func testProtocolComposition() {
print(v1 == v2)
}
4 changes: 2 additions & 2 deletions test/IRGen/marker_protocol_backdeploy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ protocol R { }
// Suppress marker protocols when forming existentials at runtime
public func takeAnyType<T>(_: T.Type) { }

// CHECK-LABEL: define {{.*}}@"$ss8Sendable_26marker_protocol_backdeploy1QAB1RpMa"
// CHECK: ss8Sendable_26marker_protocol_backdeploy1QAB1RpML
// CHECK-LABEL: define {{.*}}@"$s26marker_protocol_backdeploy1Q_AA1RpMa"
// CHECK: $s26marker_protocol_backdeploy1Q_AA1RpML
// CHECK-NOT: Sendable
// CHECK: s26marker_protocol_backdeploy1QMp
// CHECK-NOT: Sendable
Expand Down
29 changes: 29 additions & 0 deletions test/Interpreter/protocol_composition_with_markers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test

protocol P {
init()
func f()
}
class C: P {
required init() {}
func f() { print("C.f") }
}
struct G<T: P> {
func f() { T().f() }
}

G<any (C & Sendable)>().f()
// CHECK: C.f

do {
let v1 = (any C & Sendable).self
let v2 = C.self

print(v1)
// CHECK: C
print(v2)
// CHECK: C
print(v1 == v2)
// CHECK: true
}