Skip to content

Sema: Intro experimental @cdecl and basic C compatibility check #80744

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 7 commits into from
Apr 17, 2025
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
17 changes: 12 additions & 5 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -690,22 +690,29 @@ class SectionAttr : public DeclAttribute {
/// Defines the @_cdecl attribute.
class CDeclAttr : public DeclAttribute {
public:
CDeclAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
: DeclAttribute(DeclAttrKind::CDecl, AtLoc, Range, Implicit), Name(Name) {
CDeclAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit,
bool Underscored)
: DeclAttribute(DeclAttrKind::CDecl, AtLoc, Range, Implicit),
Name(Name), Underscored(Underscored) {
}

CDeclAttr(StringRef Name, bool Implicit)
: CDeclAttr(Name, SourceLoc(), SourceRange(), Implicit) {}
CDeclAttr(StringRef Name, bool Implicit, bool Underscored)
: CDeclAttr(Name, SourceLoc(), SourceRange(), Implicit, Underscored) {}

/// The symbol name.
const StringRef Name;

/// Is this the version of the attribute that's underscored?
/// Used to preserve retro compatibility with early adopters.
const bool Underscored;

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::CDecl;
}

CDeclAttr *clone(ASTContext &ctx) const {
return new (ctx) CDeclAttr(Name, AtLoc, Range, isImplicit());
return new (ctx) CDeclAttr(Name, AtLoc, Range, isImplicit(),
Underscored);
}

bool isEquivalent(const CDeclAttr *other, Decl *attachedTo) const {
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8128,6 +8128,11 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
/// instance method.
bool isObjCInstanceMethod() const;

/// Get the foreign language targeted by a @cdecl-style attribute, if any.
/// Used to abstract away the change in meaning of @cdecl vs @_cdecl while
/// formalizing the attribute.
std::optional<ForeignLanguage> getCDeclKind() const;

/// Determine whether the name of an argument is an API name by default
/// depending on the function context.
bool argumentNameIsAPIByDefault() const;
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ DECL_ATTR(_cdecl, CDecl,
OnFunc | OnAccessor,
LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr,
63)
DECL_ATTR_ALIAS(cdecl, CDecl)

SIMPLE_DECL_ATTR(usableFromInline, UsableFromInline,
OnAbstractFunction | OnVar | OnSubscript | OnNominalType | OnTypeAlias,
Expand Down
35 changes: 23 additions & 12 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2044,11 +2044,14 @@ WARNING(wrap_objc_implementation_will_become_error,none,
(DiagnosticInfo *))

ERROR(cdecl_not_at_top_level,none,
"@_cdecl can only be applied to global functions", ())
"%0 can only be applied to global functions", (DeclAttribute))
ERROR(cdecl_empty_name,none,
"@_cdecl symbol name cannot be empty", ())
"%0 symbol name cannot be empty", (DeclAttribute))
ERROR(cdecl_throws,none,
"raising errors from @_cdecl functions is not supported", ())
"raising errors from %0 functions is not supported", (DeclAttribute))
ERROR(cdecl_feature_required,none,
"@cdecl requires '-enable-experimental-feature CDecl'",
())

// @_used and @_section
ERROR(section_linkage_markers_disabled,none,
Expand Down Expand Up @@ -6499,7 +6502,10 @@ ERROR(objc_cannot_infer_name_raw_identifier,none,
(const ValueDecl *))

// If you change this, also change enum ObjCReason
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @objcMembers|marked @IBOutlet|marked @IBAction|marked @IBSegueAction|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement|marked @IBInspectable|marked @GKInspectable|in an @objc extension of a class (without @nonobjc)|in an @objc @implementation extension of a class (without final or @nonobjc)|marked @objc by an access note}"
#define OBJC_ATTR_SELECT "select{marked @cdecl|marked @_cdecl|marked dynamic|marked @objc|marked @objcMembers|marked @IBOutlet|marked @IBAction|marked @IBSegueAction|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement|marked @IBInspectable|marked @GKInspectable|in an @objc extension of a class (without @nonobjc)|in an @objc @implementation extension of a class (without final or @nonobjc)|marked @objc by an access note}"

// Keep aligned with enum ForeignLanguage
#define FOREIGN_LANG_SELECT "select{C|Objective-C}"

ERROR(objc_invalid_on_var,none,
"property cannot be %" OBJC_ATTR_SELECT "0 "
Expand Down Expand Up @@ -6579,17 +6585,21 @@ ERROR(objc_invalid_on_func_variadic,none,
"method cannot be %" OBJC_ATTR_SELECT "0 because it has a variadic "
"parameter", (unsigned))
ERROR(objc_invalid_on_func_inout,none,
"method cannot be %" OBJC_ATTR_SELECT "0 because inout "
"parameters cannot be represented in Objective-C", (unsigned))
"%kindonly0 cannot be %" OBJC_ATTR_SELECT "1 because inout "
"parameters cannot be represented in %" FOREIGN_LANG_SELECT "2",
(const AbstractFunctionDecl*, unsigned, unsigned))
ERROR(objc_invalid_on_func_param_type,none,
"method cannot be %" OBJC_ATTR_SELECT "1 because the type of the "
"parameter %0 cannot be represented in Objective-C", (unsigned, unsigned))
"%kindonly0 cannot be %" OBJC_ATTR_SELECT "2 because the type of the "
"parameter %1 cannot be represented in %" FOREIGN_LANG_SELECT "3",
(const AbstractFunctionDecl*, unsigned, unsigned, unsigned))
ERROR(objc_invalid_on_func_single_param_type,none,
"method cannot be %" OBJC_ATTR_SELECT "0 because the type of the "
"parameter cannot be represented in Objective-C", (unsigned))
"%kindonly0 cannot be %" OBJC_ATTR_SELECT "1 because the type of the "
"parameter cannot be represented in %" FOREIGN_LANG_SELECT "2",
(const AbstractFunctionDecl*, unsigned, unsigned))
ERROR(objc_invalid_on_func_result_type,none,
"method cannot be %" OBJC_ATTR_SELECT "0 because its result type "
"cannot be represented in Objective-C", (unsigned))
"%kindonly0 cannot be %" OBJC_ATTR_SELECT "1 because its result type "
"cannot be represented in %" FOREIGN_LANG_SELECT "2",
(const AbstractFunctionDecl*, unsigned, unsigned))
ERROR(objc_invalid_on_foreign_class,none,
"method cannot be %" OBJC_ATTR_SELECT "0 because Core Foundation "
"types are not classes in Objective-C", (unsigned))
Expand Down Expand Up @@ -6715,6 +6725,7 @@ ERROR(nonobjc_not_allowed,none,
#undef OBJC_DIAG_SELECT_2
#undef OBJC_DIAG_SELECT
#undef OBJC_ATTR_SELECT
#undef FOREIGN_LANG_SELECT

//------------------------------------------------------------------------------
// MARK: @exclusivity
Expand Down
19 changes: 19 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -4853,6 +4853,25 @@ class TypeCheckObjCImplementationRequest
bool isCached() const { return true; }
};

/// Check @cdecl-style attributes for compatibility with the foreign language.
class TypeCheckCDeclAttributeRequest
: public SimpleRequest<TypeCheckCDeclAttributeRequest,
evaluator::SideEffect(FuncDecl *FD,
CDeclAttr *attr),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

evaluator::SideEffect
evaluate(Evaluator &evaluator, FuncDecl *FD, CDeclAttr *attr) const;

public:
bool isCached() const { return true; }
};

void simple_display(llvm::raw_ostream &out, ASTNode node);
void simple_display(llvm::raw_ostream &out, Type value);
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,9 @@ SWIFT_REQUEST(TypeChecker, IsNonUserModuleRequest,
SWIFT_REQUEST(TypeChecker, TypeCheckObjCImplementationRequest,
unsigned(ExtensionDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, TypeCheckCDeclAttributeRequest,
evaluator::SideEffect(FuncDecl *, CDeclAttr *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, HasInitAccessorRequest,
bool(AbstractStorageDecl *), Cached,
NoLocationInfo)
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ EXPERIMENTAL_FEATURE(ClosureBodyMacro, true)
/// Allow declarations of Swift runtime symbols using @_silgen_name.
EXPERIMENTAL_FEATURE(AllowRuntimeSymbolDeclarations, true)

/// Allow use of `@cdecl`
EXPERIMENTAL_FEATURE(CDecl, false)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
4 changes: 3 additions & 1 deletion lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1783,7 +1783,9 @@ StringRef DeclAttribute::getAttrName() const {
case DeclAttrKind::Alignment:
return "_alignment";
case DeclAttrKind::CDecl:
return "_cdecl";
if (cast<CDeclAttr>(this)->Underscored)
return "_cdecl";
return "cdecl";
case DeclAttrKind::SwiftNativeObjCRuntimeBase:
return "_swift_native_objc_runtime_base";
case DeclAttrKind::Semantics:
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/Bridging/DeclAttributeBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ BridgedCDeclAttr BridgedCDeclAttr_createParsed(BridgedASTContext cContext,
BridgedStringRef cName) {
return new (cContext.unbridged())
CDeclAttr(cName.unbridged(), cAtLoc.unbridged(), cRange.unbridged(),
/*Implicit=*/false);
/*Implicit=*/false, /*Underscored*/true);
}

BridgedCustomAttr BridgedCustomAttr_createParsed(
Expand Down
9 changes: 9 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10449,6 +10449,15 @@ bool AbstractFunctionDecl::isObjCInstanceMethod() const {
return isInstanceMember() || isa<ConstructorDecl>(this);
}

std::optional<ForeignLanguage> AbstractFunctionDecl::getCDeclKind() const {
auto attr = getAttrs().getAttribute<CDeclAttr>();
if (!attr)
return std::nullopt;

return attr->Underscored ? ForeignLanguage::ObjectiveC
: ForeignLanguage::C;
}

bool AbstractFunctionDecl::needsNewVTableEntry() const {
auto &ctx = getASTContext();
return evaluateOrDefault(
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ static bool usesFeatureClosureBodyMacro(Decl *decl) {
return false;
}

static bool usesFeatureCDecl(Decl *decl) {
auto attr = decl->getAttrs().getAttribute<CDeclAttr>();
return attr && !attr->Underscored;
}

static bool usesFeatureMemorySafetyAttributes(Decl *decl) {
if (decl->getAttrs().hasAttribute<SafeAttr>() ||
decl->getAttrs().hasAttribute<UnsafeAttr>())
Expand Down
8 changes: 5 additions & 3 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3074,10 +3074,12 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
if (DK == DeclAttrKind::SILGenName)
Attributes.add(new (Context) SILGenNameAttr(AsmName.value(), Raw, AtLoc,
AttrRange, /*Implicit=*/false));
else if (DK == DeclAttrKind::CDecl)
else if (DK == DeclAttrKind::CDecl) {
bool underscored = AttrName.starts_with("_");
Attributes.add(new (Context) CDeclAttr(AsmName.value(), AtLoc,
AttrRange, /*Implicit=*/false));
else if (DK == DeclAttrKind::Expose) {
AttrRange, /*Implicit=*/false,
/*isUnderscored*/underscored));
} else if (DK == DeclAttrKind::Expose) {
for (auto *EA : Attributes.getAttributes<ExposeAttr>()) {
// A single declaration cannot have two @_exported attributes with
// the same exposure kind.
Expand Down
5 changes: 3 additions & 2 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,9 @@ configureInheritedDesignatedInitAttributes(ClassDecl *classDecl,
std::optional<ForeignAsyncConvention> asyncConvention;
std::optional<ForeignErrorConvention> errorConvention;
if (superclassCtor->isObjC() &&
!isRepresentableInObjC(ctor, ObjCReason::MemberOfObjCSubclass,
asyncConvention, errorConvention))
!isRepresentableInLanguage(ctor, ObjCReason::MemberOfObjCSubclass,
asyncConvention, errorConvention,
ForeignLanguage::ObjectiveC))
ctor->getAttrs().add(new (ctx) NonObjCAttr(/*isImplicit=*/true));
}

Expand Down
11 changes: 9 additions & 2 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2368,17 +2368,24 @@ static bool canDeclareSymbolName(StringRef symbol, ModuleDecl *fromModule) {
void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) {
// Only top-level func decls are currently supported.
if (D->getDeclContext()->isTypeContext())
diagnose(attr->getLocation(), diag::cdecl_not_at_top_level);
diagnose(attr->getLocation(), diag::cdecl_not_at_top_level,
attr);

// The name must not be empty.
if (attr->Name.empty())
diagnose(attr->getLocation(), diag::cdecl_empty_name);
diagnose(attr->getLocation(), diag::cdecl_empty_name,
attr);

// The standard library can use @_cdecl to implement runtime functions.
if (!canDeclareSymbolName(attr->Name, D->getModuleContext())) {
diagnose(attr->getLocation(), diag::reserved_runtime_symbol_name,
attr->Name);
}

if (!attr->Underscored &&
!Ctx.LangOpts.hasFeature(Feature::CDecl)) {
Ctx.Diags.diagnose(attr->getLocation(), diag::cdecl_feature_required);
}
}

void AttributeChecker::visitExposeAttr(ExposeAttr *attr) {
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/TypeCheckCaptures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -789,8 +789,10 @@ CaptureInfo CaptureInfoRequest::evaluate(Evaluator &evaluator,
std::optional<ForeignAsyncConvention> asyncConvention;
std::optional<ForeignErrorConvention> errorConvention;
if (!AFD->isObjC() &&
isRepresentableInObjC(AFD, ObjCReason::MemberOfObjCMembersClass,
asyncConvention, errorConvention)) {
isRepresentableInLanguage(AFD,
ObjCReason::MemberOfObjCMembersClass,
asyncConvention, errorConvention,
ForeignLanguage::ObjectiveC)) {
AFD->diagnose(
diag::objc_generic_extension_using_type_parameter_try_objc)
.fixItInsert(AFD->getAttributeInsertionLoc(false), "@objc ");
Expand Down
Loading