Skip to content

[ClangImporter] Allow @Sendable on more params #41737

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
Mar 15, 2022
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
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsClangImporter.def
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ WARNING(clang_error_code_must_be_sendable,none,
"cannot make error code type '%0' non-sendable because Swift errors "
"are always sendable", (StringRef))

WARNING(clang_param_ignored_sendable_attr,none,
"cannot make parameter '%0' sendable type %1 cannot be made sendable "
"by adding '@Sendable' or '& Sendable'",
(StringRef, Type))
NOTE(clang_param_should_be_implicitly_sendable,none,
"parameter should be implicitly 'Sendable' because it is a completion "
"handler", ())

WARNING(implicit_bridging_header_imported_from_module,none,
"implicit import of bridging header '%0' via module %1 "
"is deprecated and will be removed in a later version of Swift",
Expand Down
212 changes: 209 additions & 3 deletions lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "swift/AST/ParameterList.h"
#include "swift/AST/Type.h"
#include "swift/AST/Types.h"
#include "swift/AST/TypeVisitor.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/Parse/Token.h"
#include "swift/Strings.h"
Expand Down Expand Up @@ -1733,6 +1734,197 @@ ImportedType ClangImporter::Implementation::importPropertyType(
Bridgeability::Full, optionality);
}

namespace {

class GetSendableType :
private TypeVisitor<GetSendableType, std::pair<Type, bool>> {
ASTContext &ctx;

public:
GetSendableType(ASTContext &ctx) : ctx(ctx) {}

/// The result of a conversion. Contains the converted type and a \c bool that
/// is \c true if the operation found something to change, or \c false
/// otherwise.
using Result = std::pair<Type, /*found=*/bool>;

/// Returns a modified version of \p type that has been made explicitly
/// \c Sendable by adding an \c \@Sendable attribute to a function type
/// or forming a protocol composition with \c & \c Sendable.
Result convert(Type type) { return visit(type); }

private:
/// Decide how to represent the given type in a protocol composition. This
/// is specialized for \c ProtocolCompositionType to avoid nesting
/// compositions.
///
/// \param members The types to include in the composition.
/// \return \c true if the composition should include \c AnyObject, \c false
/// otherwise.
bool getAsComposition(ProtocolCompositionType *ty,
SmallVectorImpl<Type> &members) {
llvm::append_range(members, ty->getMembers());
return ty->hasExplicitAnyObject();
}

/// Decide how to represent the given type in a protocol composition. This
/// is specialized for \c ProtocolCompositionType to avoid nesting
/// compositions.
///
/// \param members The types to include in the composition.
/// \return \c true if the composition should include \c AnyObject, \c false
/// otherwise.
bool getAsComposition(TypeBase *ty, SmallVectorImpl<Type> &members) {
members.push_back(ty);
return false;
}

// MARK: Visitor Actions

/// Visitor action: Replace this type with a protocol composition that
/// includes \c Sendable.
template <typename Ty> Result compose(Ty *ty) {
SmallVector<Type, 8> members;
bool explicitAnyObject = getAsComposition(ty, members);

auto proto = ctx.getProtocol(KnownProtocolKind::Sendable);
members.push_back(proto->getDeclaredInterfaceType());

return {
ProtocolCompositionType::get(ctx, members, explicitAnyObject), true };
}

/// Visitor action: Recurse into the children of this type and try to add
/// \c Sendable to them.
Result recurse(Type ty) {
bool anyFound = false;

Type newTy = ty.transformRec([&](TypeBase *childTy) -> Optional<Type> {
// We want to visit the first level of children.
if (childTy == ty.getPointer())
return None;

auto result = this->visit(childTy);
anyFound |= result.second;
return result.first;
});

return { newTy, anyFound };
}

/// Visitor action: Ignore this type; do not modify it and do not recurse into
/// it to find other types to modify.
Result pass(Type ty, bool found = false) {
return { ty, found };
}

// Macros to define visitors based on these actions.
#define VISIT(CLASS, ACT) Result visit##CLASS(CLASS *ty) { return ACT(ty); }
#define NEVER_VISIT(CLASS) Result visit##CLASS(CLASS *ty) { \
llvm_unreachable("can't have " #CLASS " in imported clang type"); \
return pass(ty); \
}

// MARK: Visitors

friend TypeVisitor<GetSendableType, Result>;

Result visitErrorType(ErrorType *ty) {
// Pass, but suppress diagnostic about not finding anything `Sendable`.
return pass(ty, /*found=*/true);
}

NEVER_VISIT(UnresolvedType)
NEVER_VISIT(PlaceholderType)
NEVER_VISIT(BuiltinType)

VISIT(TupleType, recurse)

NEVER_VISIT(ReferenceStorageType)

VISIT(EnumType, pass)
VISIT(StructType, pass)
VISIT(ClassType, compose)
VISIT(ProtocolType, compose)

Result visitBoundGenericType(BoundGenericType *ty) {
assert(!isa<BoundGenericClassType>(ty) && "classes handled elsewhere");

// These types are produced during bridging and have conditional
// conformances to Sendable depending on their generic parameters, so we
// want to make their generic parameters `Sendable`.
if (ty->isOptional() || ty->isArray() || ty->isSet() ||
ty->isDictionary())
return recurse(ty);

// Other non-class generic types (e.g. pointers) cannot be made Sendable.
return pass(ty);
}

VISIT(BoundGenericClassType, compose)
NEVER_VISIT(UnboundGenericType)

VISIT(AnyMetatypeType, recurse)

VISIT(ModuleType, pass)
VISIT(DynamicSelfType, pass)

NEVER_VISIT(SubstitutableType)
NEVER_VISIT(DependentMemberType)

Result visitAnyFunctionType(AnyFunctionType *ty) {
auto newFn = applyToFunctionType(ty, [](ASTExtInfo extInfo) {
return extInfo.withConcurrent();
});
return { newFn, true };
}

NEVER_VISIT(SILFunctionType)
NEVER_VISIT(SILBlockStorageType)
NEVER_VISIT(SILBoxType)
NEVER_VISIT(SILTokenType)

VISIT(ProtocolCompositionType, compose)

// ProtocolCompositionType doesn't handle ParameterizedProtocolType
// correctly, but we currently never import anything with it, so forbid it
// until we find we need it.
NEVER_VISIT(ParameterizedProtocolType)

VISIT(ExistentialType, recurse)
NEVER_VISIT(LValueType)
VISIT(InOutType, recurse)

NEVER_VISIT(PackType)
NEVER_VISIT(PackExpansionType)
NEVER_VISIT(TypeVariableType)

VISIT(SugarType, recurse)

Result visitTypeAliasType(TypeAliasType *ty) {
// Try converting the underlying type.
Type underlying = ty->getSinglyDesugaredType();
auto result = visit(underlying);

// If nothing that could be made Sendable was found in the underlying type,
// keep the sugar.
if (!result.second)
return pass(ty);

// If something Sendable-capable *was* found but the operation was a no-op,
// keep the sugar but indicate that we did find something to avoid a
// diagnostic.
if (result.first->getCanonicalType() == underlying->getCanonicalType())
return pass(ty, /*found=*/true);

// We found something and it did change the type. Desugar to the converted
// underlying type.
return result;
}
};

} // anonymous namespace

Type ClangImporter::Implementation::applyParamAttributes(
const clang::ParmVarDecl *param, Type type, bool sendableByDefault) {
bool sendableRequested = sendableByDefault;
Expand Down Expand Up @@ -1780,9 +1972,23 @@ Type ClangImporter::Implementation::applyParamAttributes(
}

if (!sendableDisqualified && sendableRequested) {
type = applyToFunctionType(type, [](ASTExtInfo extInfo) {
return extInfo.withConcurrent();
});
bool changed;
std::tie(type, changed) = GetSendableType(SwiftContext).convert(type);

// Diagnose if we couldn't find a place to add `Sendable` to the type.
if (!changed) {
auto parentDecl = cast<clang::Decl>(param->getDeclContext());

addImportDiagnostic(parentDecl,
Diagnostic(diag::clang_param_ignored_sendable_attr,
param->getName(), type),
param->getLocation());

if (sendableByDefault)
addImportDiagnostic(parentDecl,
Diagnostic(diag::clang_param_should_be_implicitly_sendable),
param->getLocation());
}
}

return type;
Expand Down
53 changes: 53 additions & 0 deletions test/ClangImporter/objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,57 @@ func check() async {
_ = await BazFrame(size: 0)
}

func testSender(
sender: NXSender,
sendableObject: SendableClass,
nonSendableObject: NonSendableClass,
sendableSubclassOfNonSendableObject: NonSendableClass & Sendable,
sendableProtos: LabellyProtocol & ObjCClub & Sendable,
nonSendableProtos: LabellyProtocol & ObjCClub,
sendableGeneric: GenericObject<SendableClass> & Sendable,
nonSendableGeneric: GenericObject<SendableClass>,
ptr: UnsafeMutableRawPointer,
stringArray: [String]
) {
sender.sendAny(sendableObject)
sender.sendAny(nonSendableObject)
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}

sender.sendOptionalAny(sendableObject)
sender.sendOptionalAny(nonSendableObject)
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}

sender.sendSendable(sendableObject)

sender.sendSendableSubclasses(nonSendableObject)
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}
sender.sendSendableSubclasses(sendableSubclassOfNonSendableObject)
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}
// FIXME(rdar://89992569): Should allow for the possibility that NonSendableClass will have a Sendable subclass

sender.sendProto(sendableProtos)
sender.sendProto(nonSendableProtos)
// expected-error@-1 {{argument type 'any LabellyProtocol & ObjCClub' does not conform to expected type 'Sendable'}}
// FIXME(rdar://89992095): Should be a warning because we're in -warn-concurrency

sender.sendProtos(sendableProtos)
sender.sendProtos(nonSendableProtos)
// expected-error@-1 {{argument type 'any LabellyProtocol & ObjCClub' does not conform to expected type 'Sendable'}}
// FIXME(rdar://89992095): Should be a warning because we're in -warn-concurrency

sender.sendAnyArray([sendableObject])
sender.sendAnyArray([nonSendableObject])
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}

sender.sendGeneric(sendableGeneric)
// expected-warning@-1 {{type 'GenericObject<SendableClass>' does not conform to the 'Sendable' protocol}}
// FIXME(rdar://89992569): Should allow for the possibility that GenericObject will have a Sendable subclass
sender.sendGeneric(nonSendableGeneric)
// expected-error@-1 {{argument type 'GenericObject<SendableClass>' does not conform to expected type 'Sendable'}}
// FIXME(rdar://89992095): Should be a warning because we're in -warn-concurrency

sender.sendPtr(ptr)
sender.sendStringArray(stringArray)
}

} // SwiftStdlib 5.5
17 changes: 17 additions & 0 deletions test/IDE/print_clang_objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,20 @@ import _Concurrency
// CHECK: func doSomethingConcurrently(_ block: @Sendable () -> Void)

// CHECK: @MainActor @objc protocol TripleMainActor {

// CHECK-LABEL: class NXSender :
// CHECK-NEXT: func sendAny(_ obj: Sendable)
// CHECK-NEXT: func sendOptionalAny(_ obj: Sendable?)
// CHECK-NEXT: func sendSendable(_ sendable: SendableClass & Sendable)
// CHECK-NEXT: func sendSendableSubclasses(_ sendableSubclass: NonSendableClass & Sendable)
// CHECK-NEXT: func sendProto(_ obj: LabellyProtocol & Sendable)
// CHECK-NEXT: func sendProtos(_ obj: LabellyProtocol & ObjCClub & Sendable)
// CHECK-NEXT: func sendAnyArray(_ array: [Sendable])
// CHECK-NEXT: func sendGeneric(_ generic: GenericObject<SendableClass> & Sendable)
// CHECK-NEXT: func sendPtr(_ val: UnsafeMutableRawPointer)
Copy link
Member

Choose a reason for hiding this comment

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

Should there be a warning about dropping the explicit sendability from the ObjC decl here and for the next function decl?

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 do emit some warnings through the experimental ClangImporter diagnostic infrastructure, but I don't want to rope that into these tests at its current stage of development.

// CHECK-NEXT: func sendStringArray(_ obj: [String])
// CHECK-NEXT: func sendAnyTypedef(_ obj: Sendable)
// CHECK-NEXT: func sendAnyTypedefs(_ objs: [Sendable])
// CHECK-NEXT: func sendBlockTypedef(_ block: @escaping @Sendable (Any) -> Void)
// CHECK-NEXT: func sendBlockTypedefs(_ blocks: [@Sendable @convention(block) (Any) -> Void])
// CHECK-NEXT: func sendUnbound(_ array: [Sendable])
22 changes: 22 additions & 0 deletions test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,26 @@ typedef NSString *NonSendableStringStruct NS_EXTENSIBLE_STRING_ENUM NONSENDABLE;

ASSUME_NONSENDABLE_END

typedef id ObjectTypedef;
typedef void(^BlockTypedef)(id);

@interface NXSender : NSObject

- (void)sendAny:(SENDABLE id)obj;
- (void)sendOptionalAny:(nullable SENDABLE id)obj;
- (void)sendSendable:(SENDABLE SendableClass *)sendable;
- (void)sendSendableSubclasses:(SENDABLE NonSendableClass *)sendableSubclass;
- (void)sendProto:(SENDABLE id <LabellyProtocol>)obj;
- (void)sendProtos:(SENDABLE id <LabellyProtocol, ObjCClub>)obj;
- (void)sendAnyArray:(SENDABLE NSArray<id> *)array;
- (void)sendGeneric:(SENDABLE GenericObject<SendableClass *> *)generic;
- (void)sendPtr:(SENDABLE void *)val; // bad
- (void)sendStringArray:(SENDABLE NSArray<NSString *> *)obj; // bad
- (void)sendAnyTypedef:(SENDABLE ObjectTypedef)obj;
- (void)sendAnyTypedefs:(SENDABLE NSArray<ObjectTypedef> *)objs;
- (void)sendBlockTypedef:(SENDABLE BlockTypedef)block;
- (void)sendBlockTypedefs:(SENDABLE NSArray<BlockTypedef> *)blocks;
- (void)sendUnbound:(SENDABLE NSArray *)array;
@end

#pragma clang assume_nonnull end