From 5a57e5708c39c119829c98efd3bbcf317d1e0428 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Wed, 18 Nov 2015 12:00:26 -0800 Subject: [PATCH] Initial implementation of a @_cdecl attribute to export top-level functions to C. There's an immediate need for this in the core libs, and we have most of the necessary pieces on hand to make it easy to implement. This is an unpolished initial implementation, with the following limitations, among others: - It doesn't support bridging error conventions, - It relies on ObjC interop, - It doesn't check for symbol name collisions, - It has an underscored name with required symbol name `@cdecl("symbol_name")`, awaiting official bikeshed painting. --- include/swift/AST/Attr.def | 3 + include/swift/AST/Attr.h | 18 ++ include/swift/AST/DiagnosticsSema.def | 10 +- include/swift/Serialization/ModuleFormat.h | 11 +- lib/AST/ASTContext.cpp | 5 +- lib/AST/Attr.cpp | 7 + lib/AST/Verifier.cpp | 5 +- lib/IRGen/Linking.cpp | 2 +- lib/Parse/ParseDecl.cpp | 12 +- lib/PrintAsObjC/PrintAsObjC.cpp | 183 ++++++++++++++++----- lib/SIL/SILDeclRef.cpp | 20 ++- lib/SILGen/SILGen.cpp | 8 + lib/Sema/TypeCheckAttr.cpp | 15 ++ lib/Sema/TypeCheckDecl.cpp | 13 ++ lib/Sema/TypeCheckType.cpp | 7 +- lib/Sema/TypeChecker.h | 1 + lib/Serialization/Deserialization.cpp | 8 + lib/Serialization/Serialization.cpp | 11 +- test/PrintAsObjC/cdecl.swift | 30 ++++ test/SILGen/cdecl.swift | 43 +++++ test/attr/attr_cdecl.swift | 55 +++++++ 21 files changed, 404 insertions(+), 63 deletions(-) create mode 100644 test/PrintAsObjC/cdecl.swift create mode 100644 test/SILGen/cdecl.swift create mode 100644 test/attr/attr_cdecl.swift diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 9f74e9e5fa195..b73bec20dc964 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -239,6 +239,9 @@ SIMPLE_DECL_ATTR(indirect, Indirect, SIMPLE_DECL_ATTR(warn_unqualified_access, WarnUnqualifiedAccess, OnFunc /*| OnVar*/ | LongAttribute, 61) +DECL_ATTR(_cdecl, CDecl, + OnFunc | LongAttribute | UserInaccessible, 62) + #undef TYPE_ATTR #undef DECL_ATTR_ALIAS #undef SIMPLE_DECL_ATTR diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 9fcaa865b8266..d34c46a216a5f 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -571,6 +571,24 @@ class SILGenNameAttr : public DeclAttribute { } }; +/// Defines the @_cdecl attribute. +class CDeclAttr : public DeclAttribute { +public: + CDeclAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit) + : DeclAttribute(DAK_CDecl, AtLoc, Range, Implicit), + Name(Name) {} + + CDeclAttr(StringRef Name, bool Implicit) + : CDeclAttr(Name, SourceLoc(), SourceRange(), /*Implicit=*/true) {} + + /// The symbol name. + const StringRef Name; + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_CDecl; + } +}; + /// Defines the @_semantics attribute. class SemanticsAttr : public DeclAttribute { public: diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index b85624875f6ef..04228080753fb 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -742,6 +742,14 @@ ERROR(no_objc_tagged_pointer_not_class_protocol,sema_tcd,none, ERROR(swift_native_objc_runtime_base_not_on_root_class,sema_tcd,none, "@_swift_native_objc_runtime_base_not_on_root_class can only be applied " "to root classes", ()) + +ERROR(cdecl_not_at_top_level,sema_tcd,none, + "@_cdecl can only be applied to global functions", ()) +ERROR(cdecl_empty_name,sema_tcd,none, + "@_cdecl symbol name cannot be empty", ()) +ERROR(cdecl_throws,sema_tcd,none, + "raising errors from @_cdecl functions is not supported", ()) + ERROR(attr_methods_only,sema_tcd,none, "only methods can be declared %0", (DeclAttribute)) ERROR(access_control_in_protocol,sema_tcd,none, @@ -2344,7 +2352,7 @@ ERROR(objc_name_func_mismatch,sema_objc,none, (bool, unsigned, bool, unsigned, bool, bool)) // If you change this, also change enum ObjCReason -#define OBJC_ATTR_SELECT "select{marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override}" +#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override}" ERROR(objc_invalid_on_var,sema_objc,none, "property cannot be %" OBJC_ATTR_SELECT "0 " diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index 1a5abb62bd3a9..a7bf3b1f8cb83 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -42,7 +42,7 @@ const unsigned char MODULE_DOC_SIGNATURE[] = { 0xE2, 0x9C, 0xA8, 0x07 }; /// Serialized module format major version number. /// -/// Always 0 for Swift 1.0. +/// Always 0 for Swift 1.x and 2.x. const uint16_t VERSION_MAJOR = 0; /// Serialized module format minor version number. @@ -51,7 +51,7 @@ const uint16_t VERSION_MAJOR = 0; /// To ensure that two separate changes don't silently get merged into one /// in source control, you should also update the comment to briefly /// describe what change you made. -const uint16_t VERSION_MINOR = 222; // Last change: @_fixed_layout +const uint16_t VERSION_MINOR = 223; // Last change: @_cdecl using DeclID = Fixnum<31>; using DeclIDField = BCFixed<31>; @@ -1203,6 +1203,13 @@ namespace decls_block { BCFixed<1>, // implicit flag BCBlob // _silgen_name >; + + using CDeclDeclAttrLayout = BCRecordLayout< + CDecl_DECL_ATTR, + BCFixed<1>, // implicit flag + BCBlob // _silgen_name + >; + using AlignmentDeclAttrLayout = BCRecordLayout< Alignment_DECL_ATTR, diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index b24d491f4fa8a..be5a1adc98bcd 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1875,7 +1875,10 @@ void AbstractFunctionDecl::setForeignErrorConvention( Optional AbstractFunctionDecl::getForeignErrorConvention() const { - if (!isObjC() || !isBodyThrowing()) return None; + if (!isObjC() && !getAttrs().hasAttribute()) + return None; + if (!isBodyThrowing()) + return None; auto &conventionsMap = getASTContext().Impl.ForeignErrorConventions; auto it = conventionsMap.find(this); if (it == conventionsMap.end()) return None; diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index ad6a0be0fc831..840e5ec035536 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -327,6 +327,11 @@ void DeclAttribute::print(ASTPrinter &Printer, if (cast(this)->isEscaping()) Printer << "(escaping)"; break; + + case DAK_CDecl: + Printer << "@_cdecl(\"" << cast(this)->Name << "\")"; + break; + case DAK_ObjC: { if (Options.PrintForSIL && isImplicit()) break; @@ -421,6 +426,8 @@ StringRef DeclAttribute::getAttrName() const { return "_silgen_name"; case DAK_Alignment: return "_alignment"; + case DAK_CDecl: + return "_cdecl"; case DAK_SwiftNativeObjCRuntimeBase: return "_swift_native_objc_runtime_base"; case DAK_Semantics: diff --git a/lib/AST/Verifier.cpp b/lib/AST/Verifier.cpp index 937ecc1ac440d..9fa3f6e4e04ab 100644 --- a/lib/AST/Verifier.cpp +++ b/lib/AST/Verifier.cpp @@ -2249,8 +2249,9 @@ struct ASTNodeBase {}; abort(); } - if (AFD->getForeignErrorConvention() && !AFD->isObjC()) { - Out << "foreign error convention on non-@objc function\n"; + if (AFD->getForeignErrorConvention() + && !AFD->isObjC() && !AFD->getAttrs().hasAttribute()) { + Out << "foreign error convention on non-@objc, non-@_cdecl function\n"; AFD->dump(Out); abort(); } diff --git a/lib/IRGen/Linking.cpp b/lib/IRGen/Linking.cpp index aa923d337410f..6510fb8c2e515 100644 --- a/lib/IRGen/Linking.cpp +++ b/lib/IRGen/Linking.cpp @@ -220,7 +220,7 @@ void LinkEntity::mangle(raw_ostream &buffer) const { // entity ::= declaration // other declaration case Kind::Function: - // As a special case, functions can have external asm names. + // As a special case, functions can have manually mangled names. if (auto AsmA = getDecl()->getAttrs().getAttribute()) { buffer << AsmA->Name; return; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 2100ade9ef6db..afcd8d783f6ea 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -563,6 +563,7 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, break; } + case DAK_CDecl: case DAK_SILGenName: { if (!consumeIf(tok::l_paren)) { diagnose(Loc, diag::attr_expected_lparen, AttrName, @@ -599,9 +600,16 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, diagnose(Loc, diag::attr_only_at_non_local_scope, AttrName); } - if (!DiscardAttribute) - Attributes.add(new (Context) SILGenNameAttr(AsmName.getValue(), AtLoc, + if (!DiscardAttribute) { + if (DK == DAK_SILGenName) + Attributes.add(new (Context) SILGenNameAttr(AsmName.getValue(), AtLoc, + AttrRange, /*Implicit=*/false)); + else if (DK == DAK_CDecl) + Attributes.add(new (Context) CDeclAttr(AsmName.getValue(), AtLoc, AttrRange, /*Implicit=*/false)); + else + llvm_unreachable("out of sync with switch"); + } break; } diff --git a/lib/PrintAsObjC/PrintAsObjC.cpp b/lib/PrintAsObjC/PrintAsObjC.cpp index 18fdb739fa656..6177d9df8b687 100644 --- a/lib/PrintAsObjC/PrintAsObjC.cpp +++ b/lib/PrintAsObjC/PrintAsObjC.cpp @@ -89,7 +89,8 @@ static Identifier getNameForObjC(const NominalTypeDecl *NTD, namespace { class ObjCPrinter : private DeclVisitor, private TypeVisitor> { + Optional> +{ friend ASTVisitor; friend TypeVisitor; @@ -118,7 +119,8 @@ class ObjCPrinter : private DeclVisitor, } bool shouldInclude(const ValueDecl *VD) { - return VD->isObjC() && VD->getFormalAccess() >= minRequiredAccess && + return (VD->isObjC() || VD->getAttrs().hasAttribute()) && + VD->getFormalAccess() >= minRequiredAccess && !(isa(VD) && cast(VD)->hasStubImplementation()); } @@ -306,48 +308,52 @@ class ObjCPrinter : private DeclVisitor, return true; } - void printAbstractFunction(AbstractFunctionDecl *AFD, bool isClassMethod, - bool isNSUIntegerSubscript = false) { - printDocumentationComment(AFD); - if (isClassMethod) - os << "+ ("; - else - os << "- ("; - - const clang::ObjCMethodDecl *clangMethod = nullptr; - if (!isNSUIntegerSubscript) { - if (const AbstractFunctionDecl *clangBase = findClangBase(AFD)) { - clangMethod = - dyn_cast_or_null(clangBase->getClangDecl()); - } - } - - Type rawMethodTy = AFD->getType()->castTo()->getResult(); - auto methodTy = rawMethodTy->castTo(); - + Type getForeignResultType(AbstractFunctionDecl *AFD, + FunctionType *methodTy, + Optional errorConvention) { // A foreign error convention can affect the result type as seen in // Objective-C. - Optional errorConvention - = AFD->getForeignErrorConvention(); - Type resultTy = methodTy->getResult(); if (errorConvention) { switch (errorConvention->getKind()) { case ForeignErrorConvention::ZeroResult: case ForeignErrorConvention::NonZeroResult: // The error convention provides the result type. - resultTy = errorConvention->getResultType(); - break; + return errorConvention->getResultType(); case ForeignErrorConvention::NilResult: // Errors are propagated via 'nil' returns. - resultTy = OptionalType::get(resultTy); - break; + return OptionalType::get(methodTy->getResult()); case ForeignErrorConvention::NonNilError: case ForeignErrorConvention::ZeroPreservedResult: break; } } + return methodTy->getResult(); + } + + void printAbstractFunctionAsMethod(AbstractFunctionDecl *AFD, + bool isClassMethod, + bool isNSUIntegerSubscript = false) { + printDocumentationComment(AFD); + if (isClassMethod) + os << "+ ("; + else + os << "- ("; + + const clang::ObjCMethodDecl *clangMethod = nullptr; + if (!isNSUIntegerSubscript) { + if (const AbstractFunctionDecl *clangBase = findClangBase(AFD)) { + clangMethod = + dyn_cast_or_null(clangBase->getClangDecl()); + } + } + + Optional errorConvention + = AFD->getForeignErrorConvention(); + Type rawMethodTy = AFD->getType()->castTo()->getResult(); + auto methodTy = rawMethodTy->castTo(); + auto resultTy = getForeignResultType(AFD, methodTy, errorConvention); // Constructors and methods returning DynamicSelf return // instancetype. @@ -457,15 +463,73 @@ class ObjCPrinter : private DeclVisitor, os << ";\n"; } + + void printSingleFunctionParam(const Pattern *param) { + // The type name may be multi-part. + PrintMultiPartType multiPart(*this); + visitPart(param->getType(), OTK_None); + if (isa(param)) + return; + + os << ' '; + + Identifier name = cast(param)->getBodyName(); + os << name; + if (isClangKeyword(name)) + os << "_"; + } + void printAbstractFunctionAsFunction(FuncDecl *FD) { + printDocumentationComment(FD); + Optional errorConvention + = FD->getForeignErrorConvention(); + auto resultTy = getForeignResultType(FD, + FD->getType()->castTo(), + errorConvention); + + // The result type may be a partial function type we need to close + // up later. + PrintMultiPartType multiPart(*this); + visitPart(resultTy, OTK_None); + + assert(FD->getAttrs().hasAttribute() + && "not a cdecl function"); + + os << ' ' << FD->getAttrs().getAttribute()->Name << '('; + + auto bodyPatterns = FD->getBodyParamPatterns(); + assert(bodyPatterns.size() == 1 && "not a C-compatible func"); + + if (auto *paramParen = dyn_cast(bodyPatterns.back())) { + printSingleFunctionParam(paramParen->getSemanticsProvidingPattern()); + } else if (auto *paramTuple = dyn_cast(bodyPatterns.back())) { + interleave(paramTuple->getElements(), + [&](const TuplePatternElt &elt) { + printSingleFunctionParam(elt.getPattern() + ->getSemanticsProvidingPattern()); + }, + [&]{ os << ", "; }); + } else { + llvm_unreachable("invalid param pattern"); + } + + os << ')'; + + // Finish the result type. + multiPart.finish(); + + os << ';'; + } + void visitFuncDecl(FuncDecl *FD) { - assert(FD->getDeclContext()->isTypeContext() && - "cannot handle free functions right now"); - printAbstractFunction(FD, FD->isStatic()); + if (FD->getDeclContext()->isTypeContext()) + printAbstractFunctionAsMethod(FD, FD->isStatic()); + else + printAbstractFunctionAsFunction(FD); } void visitConstructorDecl(ConstructorDecl *CD) { - printAbstractFunction(CD, false); + printAbstractFunctionAsMethod(CD, false); } bool maybePrintIBOutletCollection(Type ty) { @@ -507,9 +571,9 @@ class ObjCPrinter : private DeclVisitor, if (VD->isStatic()) { // Objective-C doesn't have class properties. Just print the accessors. - printAbstractFunction(VD->getGetter(), true); + printAbstractFunctionAsMethod(VD->getGetter(), true); if (auto setter = VD->getSetter()) - printAbstractFunction(setter, true); + printAbstractFunctionAsMethod(setter, true); return; } @@ -630,9 +694,9 @@ class ObjCPrinter : private DeclVisitor, isNSUIntegerSubscript = isNSUInteger(indexParam->getType()); } - printAbstractFunction(SD->getGetter(), false, isNSUIntegerSubscript); + printAbstractFunctionAsMethod(SD->getGetter(), false, isNSUIntegerSubscript); if (auto setter = SD->getSetter()) - printAbstractFunction(setter, false, isNSUIntegerSubscript); + printAbstractFunctionAsMethod(setter, false, isNSUIntegerSubscript); } /// Visit part of a type, such as the base of a pointer type. @@ -1148,6 +1212,33 @@ class ObjCPrinter : private DeclVisitor, Optional optionalKind) { visitPart(RST->getReferentType(), optionalKind); } + + /// RAII class for printing multi-part C types, such as functions and arrays. + class PrintMultiPartType { + ObjCPrinter &Printer; + decltype(ObjCPrinter::openFunctionTypes) savedFunctionTypes; + + PrintMultiPartType(const PrintMultiPartType &) = delete; + public: + PrintMultiPartType(ObjCPrinter &Printer) + : Printer(Printer) { + savedFunctionTypes.swap(Printer.openFunctionTypes); + } + + void finish() { + auto &openFunctionTypes = Printer.openFunctionTypes; + while (!openFunctionTypes.empty()) { + const FunctionType *openFunctionTy = openFunctionTypes.pop_back_val(); + Printer.finishFunctionType(openFunctionTy); + } + openFunctionTypes = std::move(savedFunctionTypes); + savedFunctionTypes.clear(); + } + + ~PrintMultiPartType() { + finish(); + } + }; /// Print a full type, optionally declaring the given \p name. /// @@ -1159,18 +1250,10 @@ class ObjCPrinter : private DeclVisitor, StringRef name = "") { PrettyStackTraceType trace(M.getASTContext(), "printing", ty); - decltype(openFunctionTypes) savedFunctionTypes; - savedFunctionTypes.swap(openFunctionTypes); - + PrintMultiPartType multiPart(*this); visitPart(ty, optionalKind); if (!name.empty()) os << ' ' << name; - while (!openFunctionTypes.empty()) { - const FunctionType *openFunctionTy = openFunctionTypes.pop_back_val(); - finishFunctionType(openFunctionTy); - } - - openFunctionTypes = std::move(savedFunctionTypes); } }; @@ -1460,6 +1543,14 @@ class ModuleWriter { printer.print(CD); return true; } + + bool writeFunc(const FuncDecl *FD) { + if (addImport(FD)) + return true; + + printer.print(FD); + return true; + } bool writeProtocol(const ProtocolDecl *PD) { if (addImport(PD)) @@ -1800,6 +1891,8 @@ class ModuleWriter { success = writeProtocol(PD); else if (auto ED = dyn_cast(D)) success = writeEnum(ED); + else if (auto ED = dyn_cast(D)) + success = writeFunc(ED); else llvm_unreachable("unknown top-level ObjC value decl"); diff --git a/lib/SIL/SILDeclRef.cpp b/lib/SIL/SILDeclRef.cpp index f4a760370de65..4da0f2cd6341a 100644 --- a/lib/SIL/SILDeclRef.cpp +++ b/lib/SIL/SILDeclRef.cpp @@ -246,7 +246,10 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const { // Currying and calling convention thunks have shared linkage. if (isThunk()) - return SILLinkage::Shared; + // If a function declares a @_cdecl name, its native-to-foreign thunk is + // exported with the visibility of the function. + if (!isNativeToForeignThunk() || !d->getAttrs().hasAttribute()) + return SILLinkage::Shared; // Declarations imported from Clang modules have shared linkage. // FIXME: They shouldn't. @@ -402,13 +405,20 @@ static void mangleConstant(SILDeclRef c, llvm::raw_ostream &buffer, return; } - // As a special case, functions can have external asm names. - // Use the asm name only for the original non-thunked, non-curried entry + // As a special case, functions can have manually mangled names. + // Use the SILGen name only for the original non-thunked, non-curried entry // point. - if (auto AsmA = c.getDecl()->getAttrs().getAttribute()) + if (auto NameA = c.getDecl()->getAttrs().getAttribute()) if (!c.isForeignToNativeThunk() && !c.isNativeToForeignThunk() && !c.isCurried) { - buffer << AsmA->Name; + buffer << NameA->Name; + return; + } + + // Use a given cdecl name for native-to-foreign thunks. + if (auto CDeclA = c.getDecl()->getAttrs().getAttribute()) + if (c.isNativeToForeignThunk()) { + buffer << CDeclA->Name; return; } diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 58af77424431d..a20acdaae76bc 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -432,6 +432,14 @@ void SILGenModule::emitAbstractFuncDecl(AbstractFunctionDecl *AFD) { if (!Captures.empty()) TopLevelSGF->B.createMarkFunctionEscape(AFD, Captures); } + + // If the declaration is exported as a C function, emit its native-to-foreign + // thunk too, if it wasn't already forced. + if (AFD->getAttrs().hasAttribute()) { + auto thunk = SILDeclRef(AFD).asForeign(); + if (!hasFunction(thunk)) + emitNativeToForeignThunk(thunk); + } } void SILGenModule::emitFunction(FuncDecl *fd) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index c7136e0302201..cc96cc64da1e1 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -47,6 +47,7 @@ class AttributeEarlyChecker : public AttributeVisitor { bool visitDeclAttribute(DeclAttribute *A) = delete; #define IGNORED_ATTR(X) void visit##X##Attr(X##Attr *) {} + IGNORED_ATTR(CDecl) IGNORED_ATTR(SILGenName) IGNORED_ATTR(Available) IGNORED_ATTR(Convenience) @@ -648,6 +649,8 @@ class AttributeChecker : public AttributeVisitor { #undef IGNORED_ATTR void visitAvailableAttr(AvailableAttr *attr); + + void visitCDeclAttr(CDeclAttr *attr); void visitFinalAttr(FinalAttr *attr); void visitIBActionAttr(IBActionAttr *attr); @@ -862,6 +865,18 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) { } } +void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) { + // Only top-level func decls are currently supported. + if (D->getDeclContext()->isTypeContext()) + TC.diagnose(attr->getLocation(), + diag::cdecl_not_at_top_level); + + // The name must not be empty. + if (attr->Name.empty()) + TC.diagnose(attr->getLocation(), + diag::cdecl_empty_name); +} + void AttributeChecker::visitUnsafeNoObjCTaggedPointerAttr( UnsafeNoObjCTaggedPointerAttr *attr) { // Only class protocols can have the attribute. diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 2e23496d2ca4c..5b328e1fa2255 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -4105,6 +4105,18 @@ class DeclChecker : public DeclVisitor { markAsObjC(TC, FD, isObjC, errorConvention); } + // If the function is exported to C, it must be representable in (Obj-)C. + if (auto CDeclAttr = FD->getAttrs().getAttribute()) { + Optional errorConvention; + if (TC.isRepresentableInObjC(FD, ObjCReason::ExplicitlyCDecl, + errorConvention)) { + if (FD->isBodyThrowing()) { + FD->setForeignErrorConvention(*errorConvention); + TC.diagnose(CDeclAttr->getLocation(), diag::cdecl_throws); + } + } + } + inferDynamic(TC.Context, FD); TC.checkDeclAttributes(FD); @@ -4778,6 +4790,7 @@ class DeclChecker : public DeclVisitor { UNINTERESTING_ATTR(Accessibility) UNINTERESTING_ATTR(Alignment) + UNINTERESTING_ATTR(CDecl) UNINTERESTING_ATTR(SILGenName) UNINTERESTING_ATTR(Exported) UNINTERESTING_ATTR(IBAction) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 436a98916fe6b..ced97cbb18a85 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -2672,9 +2672,10 @@ bool TypeChecker::isRepresentableInObjC( if (auto *FD = dyn_cast(AFD)) { if (FD->isAccessor()) { // Accessors can only be @objc if the storage declaration is. + // Global computed properties may however @_cdecl their accessors. auto storage = FD->getAccessorStorageDecl(); validateDecl(storage); - if (!storage->isObjC()) { + if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl) { if (Diagnose) { auto error = FD->isGetter() ? (isa(storage) @@ -2730,7 +2731,7 @@ bool TypeChecker::isRepresentableInObjC( if (!isSpecialInit && !isParamPatternRepresentableInObjC(*this, AFD, - AFD->getBodyParamPatterns()[1], + AFD->getBodyParamPatterns().back(), Reason)) { if (!Diagnose) { // Return as soon as possible if we are not producing diagnostics. @@ -2866,7 +2867,7 @@ bool TypeChecker::isRepresentableInObjC( // If the selector did not provide an index for the error, find // the last parameter that is not a trailing closure. if (!foundErrorParameterIndex) { - const Pattern *paramPattern = AFD->getBodyParamPatterns()[1]; + const Pattern *paramPattern = AFD->getBodyParamPatterns().back(); if (auto *tuple = dyn_cast(paramPattern)) { errorParameterIndex = tuple->getNumElements(); while (errorParameterIndex > 0 && diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 0c942d6abb096..ed281b18a06fb 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -391,6 +391,7 @@ withoutContext(TypeResolutionOptions options) { /// the OBJC_ATTR_SELECT macro in DiagnosticsSema.def. enum class ObjCReason { DoNotDiagnose, + ExplicitlyCDecl, ExplicitlyDynamic, ExplicitlyObjC, ExplicitlyIBOutlet, diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 62b719464de90..7c1e2ec138416 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -1797,6 +1797,14 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional ForcedContext) { break; } + case decls_block::CDecl_DECL_ATTR: { + bool isImplicit; + serialization::decls_block::CDeclDeclAttrLayout::readRecord( + scratch, isImplicit); + Attr = new (ctx) CDeclAttr(blobData, isImplicit); + break; + } + case decls_block::Alignment_DECL_ATTR: { bool isImplicit; unsigned alignment; diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 09d09dc5ee37c..34c742ad6b334 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1560,7 +1560,16 @@ void Serializer::writeDeclAttribute(const DeclAttribute *DA) { theAttr->Name); return; } - + + case DAK_CDecl: { + auto *theAttr = cast(DA); + auto abbrCode = DeclTypeAbbrCodes[CDeclDeclAttrLayout::Code]; + CDeclDeclAttrLayout::emitRecord(Out, ScratchRecord, abbrCode, + theAttr->isImplicit(), + theAttr->Name); + return; + } + case DAK_Alignment: { auto *theAlignment = cast(DA); auto abbrCode = DeclTypeAbbrCodes[AlignmentDeclAttrLayout::Code]; diff --git a/test/PrintAsObjC/cdecl.swift b/test/PrintAsObjC/cdecl.swift new file mode 100644 index 0000000000000..1d99c595ab65a --- /dev/null +++ b/test/PrintAsObjC/cdecl.swift @@ -0,0 +1,30 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-source-import -emit-module -emit-module-doc -o %t %s -disable-objc-attr-requires-foundation-module +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -parse-as-library %t/cdecl.swiftmodule -parse -emit-objc-header-path %t/cdecl.h -import-objc-header %S/../Inputs/empty.h -disable-objc-attr-requires-foundation-module +// RUN: FileCheck %s < %t/cdecl.h +// RUN: %check-in-clang %t/cdecl.h +// RUN: %check-in-clang -fno-modules %t/cdecl.h -include Foundation.h -include ctypes.h -include CoreFoundation.h + +// REQUIRES: objc_interop + +// CHECK-LABEL: /// What a nightmare! +// CHECK-LABEL: double (^ __nonnull block_nightmare(float (^ __nonnull x)(NSInteger)))(char); + +/// What a nightmare! +@_cdecl("block_nightmare") +func block_nightmare(x: @convention(block) Int -> Float) + -> @convention(block) CChar -> Double { return { _ in 0 } } + +// CHECK-LABEL: void foo_bar(NSInteger x, NSInteger y); +@_cdecl("foo_bar") +func foo(x: Int, bar y: Int) {} + +// CHECK-LABEL: double (* __nonnull function_pointer_nightmare(float (* __nonnull x)(NSInteger)))(char); +@_cdecl("function_pointer_nightmare") +func function_pointer_nightmare(x: @convention(c) Int -> Float) + -> @convention(c) CChar -> Double { return { _ in 0 } } + +// CHECK-LABEL: void has_keyword_arg_names(NSInteger auto_, NSInteger union_); +@_cdecl("has_keyword_arg_names") +func keywordArgNames(auto: Int, union: Int) {} diff --git a/test/SILGen/cdecl.swift b/test/SILGen/cdecl.swift new file mode 100644 index 0000000000000..c5344bb04e315 --- /dev/null +++ b/test/SILGen/cdecl.swift @@ -0,0 +1,43 @@ +// RUN: %target-swift-frontend -emit-silgen %s | FileCheck %s + +// CHECK-LABEL: sil hidden @pear : $@convention(c) +// CHECK: function_ref @_TF5cdecl5apple +// CHECK-LABEL: sil hidden @_TF5cdecl5apple +@_cdecl("pear") +func apple(f: @convention(c) Int -> Int) { +} + +// CHECK-LABEL: sil hidden @_TF5cdecl16forceCEntryPoint +// CHECK: function_ref @grapefruit +func forceCEntryPoint() { + apple(orange) +} + +// CHECK-LABEL: sil hidden @grapefruit : $@convention(c) +// CHECK: function_ref @_TF5cdecl6orange +// CHECK-LABEL: sil hidden @_TF5cdecl6orange +@_cdecl("grapefruit") +func orange(x: Int) -> Int { + return x +} + +// CHECK-LABEL: sil @cauliflower : $@convention(c) +// CHECK: function_ref @_TF5cdecl8broccoli +// CHECK-LABEL: sil @_TF5cdecl8broccoli +@_cdecl("cauliflower") +public func broccoli(x: Int) -> Int { + return x +} + +// CHECK-LABEL: sil private @collard_greens : $@convention(c) +// CHECK: function_ref @_TF5cdeclP[[PRIVATE:.*]]4kale +// CHECK: sil private @_TF5cdeclP[[PRIVATE:.*]]4kale +@_cdecl("collard_greens") +private func kale(x: Int) -> Int { + return x +} + +/* TODO: Handle error conventions +@_cdecl("vomits") +func barfs() throws {} + */ diff --git a/test/attr/attr_cdecl.swift b/test/attr/attr_cdecl.swift new file mode 100644 index 0000000000000..f10f69679607e --- /dev/null +++ b/test/attr/attr_cdecl.swift @@ -0,0 +1,55 @@ +// RUN: %target-parse-verify-swift + +@_cdecl("cdecl_foo") func foo(x: Int) -> Int { return x } + +@_cdecl("") // expected-error{{symbol name cannot be empty}} +func emptyName(x: Int) -> Int { return x } + +@_cdecl("noBody") +func noBody(x: Int) -> Int // expected-error{{expected '{' in body of function}} + +@_cdecl("property") // expected-error{{may only be used on 'func' declarations}} +var property: Int + +var computed: Int { + @_cdecl("get_computed") get { return 0 } + @_cdecl("set_computed") set { return } +} + +struct SwiftStruct { var x, y: Int } +enum SwiftEnum { case A, B } +@objc enum CEnum: Int { case A, B } + +@_cdecl("swiftStruct") +func swiftStruct(x: SwiftStruct) {} // expected-error{{cannot be represented}} expected-note{{Swift struct}} + +@_cdecl("swiftEnum") +func swiftEnum(x: SwiftEnum) {} // expected-error{{cannot be represented}} expected-note{{non-'@objc' enum}} + +@_cdecl("cEnum") +func cEnum(x: CEnum) {} + +class Foo { + @_cdecl("Foo_foo") // expected-error{{can only be applied to global functions}} + func foo(x: Int) -> Int { return x } + + @_cdecl("Foo_foo_2") // expected-error{{can only be applied to global functions}} + static func foo(x: Int) -> Int { return x } + + @_cdecl("Foo_init") // expected-error{{may only be used on 'func'}} + init() {} + + @_cdecl("Foo_deinit") // expected-error{{may only be used on 'func'}} + deinit {} +} + +func hasNested() { + @_cdecl("nested") // expected-error{{can only be used in a non-local scope}} + func nested() { } +} + +// TODO: Handle error conventions in SILGen for toplevel functions. +@_cdecl("throwing") // expected-error{{raising errors from @_cdecl functions is not supported}} +func throwing() throws { } + +// TODO: cdecl name collisions