Skip to content

Parser: Accept @cdecl with an optional identifier for a custom C name #82194

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -970,11 +970,12 @@ BridgedBackDeployedAttr BridgedBackDeployedAttr_createParsed(
BridgedSourceRange cRange, BridgedPlatformKind cPlatform,
BridgedVersionTuple cVersion);

SWIFT_NAME("BridgedCDeclAttr.createParsed(_:atLoc:range:name:)")
SWIFT_NAME("BridgedCDeclAttr.createParsed(_:atLoc:range:name:underscored:)")
BridgedCDeclAttr BridgedCDeclAttr_createParsed(BridgedASTContext cContext,
BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange,
BridgedStringRef cName);
BridgedStringRef cName,
bool underscored);

SWIFT_NAME(
"BridgedCustomAttr.createParsed(_:atLoc:type:initContext:argumentList:)")
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,9 @@ ERROR(attr_expected_comma,none,
ERROR(attr_expected_string_literal,none,
"expected string literal in '%0' attribute", (StringRef))

ERROR(attr_expected_cname,none,
"expected C identifier in '%0' attribute", (StringRef))

ERROR(attr_expected_option_such_as,none,
"expected '%0' option such as '%1'", (StringRef, StringRef))

Expand Down
5 changes: 3 additions & 2 deletions lib/AST/Bridging/DeclAttributeBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,11 @@ BridgedBackDeployedAttr BridgedBackDeployedAttr_createParsed(
BridgedCDeclAttr BridgedCDeclAttr_createParsed(BridgedASTContext cContext,
BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange,
BridgedStringRef cName) {
BridgedStringRef cName,
bool underscored) {
return new (cContext.unbridged())
CDeclAttr(cName.unbridged(), cAtLoc.unbridged(), cRange.unbridged(),
/*Implicit=*/false, /*Underscored*/true);
/*Implicit=*/false, /*Underscored*/underscored);
}

BridgedCustomAttr BridgedCustomAttr_createParsed(
Expand Down
5 changes: 4 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4543,7 +4543,10 @@ StringRef ValueDecl::getCDeclName() const {

// Handle explicit cdecl attributes.
if (auto cdeclAttr = getAttrs().getAttribute<CDeclAttr>()) {
return cdeclAttr->Name;
if (!cdeclAttr->Name.empty())
return cdeclAttr->Name;
else
return getBaseIdentifier().str();
}

return "";
Expand Down
35 changes: 26 additions & 9 deletions lib/ASTGen/Sources/ASTGen/DeclAttrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -535,20 +535,37 @@ extension ASTGenVisitor {
/// E.g.:
/// ```
/// @_cdecl("c_function_name")
/// @cdecl(c_function_name)
/// @cdecl
/// ```
func generateCDeclAttr(attribute node: AttributeSyntax) -> BridgedCDeclAttr? {
self.generateWithLabeledExprListArguments(attribute: node) { args in
guard let name = self.generateConsumingSimpleStringLiteralAttrOption(args: &args) else {
let attrName = node.attributeName.as(IdentifierTypeSyntax.self)?.name.text
let underscored = attrName?.hasPrefix("_") ?? false

var name: BridgedStringRef? = nil
if node.arguments != nil || underscored {
name = self.generateWithLabeledExprListArguments(attribute: node) {
args in
if underscored {
self.generateConsumingSimpleStringLiteralAttrOption(args: &args)
} else {
self.generateConsumingPlainIdentifierAttrOption(args: &args) {
return $0.rawText.bridged
}
}
}
guard name != nil else {
return nil
}

return .createParsed(
self.ctx,
atLoc: self.generateSourceLoc(node.atSign),
range: self.generateAttrSourceRange(node),
name: name
)
}

return .createParsed(
self.ctx,
atLoc: self.generateSourceLoc(node.atSign),
range: self.generateAttrSourceRange(node),
name: name ?? "",
underscored: underscored
)
}

struct GeneratedDerivativeOriginalDecl {
Expand Down
40 changes: 39 additions & 1 deletion lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3014,7 +3014,45 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
break;
}

case DeclAttrKind::CDecl:
case DeclAttrKind::CDecl: {
if (!AttrName.starts_with("_") &&

// Backwards support for @cdecl("stringId"). Remove before enabling in
// production so we accept only the identifier format.
lookahead<bool>(1, [&](CancellableBacktrackingScope &) {
return Tok.isNot(tok::string_literal);
})) {

std::optional<StringRef> CName;
if (consumeIfAttributeLParen()) {
// Custom C name.
if (Tok.isNot(tok::identifier)) {
diagnose(Loc, diag::attr_expected_cname, AttrName);
return makeParserSuccess();
}

CName = Tok.getText();
consumeToken(tok::identifier);
AttrRange = SourceRange(Loc, Tok.getRange().getStart());

if (!consumeIf(tok::r_paren)) {
diagnose(Loc, diag::attr_expected_rparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
}
} else {
AttrRange = SourceRange(Loc);
}

Attributes.add(new (Context) CDeclAttr(CName.value_or(StringRef()), AtLoc,
AttrRange, /*Implicit=*/false,
/*isUnderscored*/false));
break;
}

// Leave legacy @_cdecls to the logic expecting a string.
LLVM_FALLTHROUGH;
}
case DeclAttrKind::Expose:
case DeclAttrKind::SILGenName: {
if (!consumeIfAttributeLParen()) {
Expand Down
2 changes: 1 addition & 1 deletion lib/SIL/IR/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,7 @@ std::string SILDeclRef::mangle(ManglingKind MKind) const {
if (!clangMangling.empty())
return clangMangling;
}
return CDeclA->Name.str();
return getDecl()->getCDeclName().str();
}

if (SKind == ASTMangler::SymbolKind::DistributedThunk) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2387,8 +2387,8 @@ void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) {
diagnose(attr->getLocation(), diag::cdecl_not_at_top_level,
attr);

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

Expand Down
8 changes: 7 additions & 1 deletion test/ASTGen/attrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// RUN: -enable-experimental-feature LifetimeDependence \
// RUN: -enable-experimental-feature RawLayout \
// RUN: -enable-experimental-feature SymbolLinkageMarkers \
// RUN: -enable-experimental-feature CDecl \
// RUN: -enable-experimental-concurrency \
// RUN: -enable-experimental-move-only \
// RUN: -enable-experimental-feature ParserASTGen \
Expand All @@ -15,6 +16,7 @@
// RUN: -enable-experimental-feature LifetimeDependence \
// RUN: -enable-experimental-feature RawLayout \
// RUN: -enable-experimental-feature SymbolLinkageMarkers \
// RUN: -enable-experimental-feature CDecl \
// RUN: -enable-experimental-concurrency \
// RUN: -enable-experimental-move-only \
// RUN: | %sanitize-address > %t/cpp-parser.ast
Expand All @@ -28,6 +30,7 @@
// RUN: -enable-experimental-feature LifetimeDependence \
// RUN: -enable-experimental-feature RawLayout \
// RUN: -enable-experimental-feature SymbolLinkageMarkers \
// RUN: -enable-experimental-feature CDecl \
// RUN: -enable-experimental-concurrency \
// RUN: -enable-experimental-move-only

Expand All @@ -39,6 +42,7 @@
// REQUIRES: swift_feature_LifetimeDependence
// REQUIRES: swift_feature_RawLayout
// REQUIRES: swift_feature_SymbolLinkageMarkers
// REQUIRES: swift_feature_CDecl

// rdar://116686158
// UNSUPPORTED: asan
Expand Down Expand Up @@ -95,7 +99,9 @@ func fn(_: Int) {}

@_disallowFeatureSuppression(NoncopyableGenerics) public struct LoudlyNC<T: ~Copyable> {}

@_cdecl("c_function_name") func foo(x: Int) {}
@_cdecl("c_function_name") func cdeclUnderscore(x: Int) {}
@cdecl(c_function_name_official) func cdecl(x: Int) {}
@cdecl func cdeclDefault() {}

struct StaticProperties {
dynamic var property: Int { return 1 }
Expand Down
8 changes: 6 additions & 2 deletions test/PrintAsObjC/cdecl-official.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@
// CHECK: #endif

/// My documentation
@cdecl("simple")
func a_simple(x: Int, bar y: Int) -> Int { return x }
@cdecl(simple)
func a0_simple(x: Int, bar y: Int) -> Int { return x }
// CHECK-LABEL: // My documentation
// CHECK-LABEL: SWIFT_EXTERN ptrdiff_t simple(ptrdiff_t x, ptrdiff_t y) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;

@cdecl
func a1_defaultName(x: Int) -> Int { return x }
// CHECK-LABEL: SWIFT_EXTERN ptrdiff_t a1_defaultName(ptrdiff_t x) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;

@cdecl("primitiveTypes")
public func b_primitiveTypes(i: Int, ci: CInt, l: CLong, c: CChar, f: Float, d: Double, b: Bool) {}
// CHECK-LABEL: SWIFT_EXTERN void primitiveTypes(ptrdiff_t i, int ci, long l, char c, float f, double d, bool b) SWIFT_NOEXCEPT;
Expand Down
55 changes: 55 additions & 0 deletions test/SILGen/cdecl-official.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-emit-silgen %s -module-name cdecl \
// RUN: -enable-experimental-feature CDecl > %t/out.sil
// RUN: %FileCheck %s -input-file %t/out.sil

// REQUIRES: swift_feature_CDecl

// CHECK-LABEL: sil hidden [thunk] [ossa] @pear : $@convention(c)
// CHECK: function_ref @$s5cdecl5apple{{[_0-9a-zA-Z]*}}F
// CHECK-LABEL: sil hidden [ossa] @$s5cdecl5apple{{[_0-9a-zA-Z]*}}F
@cdecl(pear)
func apple(_ f: @convention(c) (Int) -> Int) { }

// CHECK-LABEL: sil hidden [ossa] @$s5cdecl16forceCEntryPoint{{[_0-9a-zA-Z]*}}F
// CHECK: function_ref @grapefruit : $@convention(c) (Int) -> Int
// CHECK: function_ref apple(_:)
// CHECK: function_ref @$s5cdecl5appleyyS2iXCF : $@convention(thin) (@convention(c) (Int) -> Int) -> ()
// FIXME should it be function_ref @apple?
func forceCEntryPoint() {
apple(orange)
}

// CHECK-LABEL: sil hidden [thunk] [ossa] @grapefruit : $@convention(c)
// CHECK: function_ref @$s5cdecl6orange{{[_0-9a-zA-Z]*}}F
// CHECK-LABEL: sil hidden [ossa] @$s5cdecl6orange{{[_0-9a-zA-Z]*}}F
@cdecl(grapefruit)
func orange(_ x: Int) -> Int {
return x
}

// CHECK-LABEL: sil [serialized] [thunk] [ossa] @cauliflower : $@convention(c)
// CHECK: function_ref @$s5cdecl8broccoli{{[_0-9a-zA-Z]*}}F
// CHECK-LABEL: sil [ossa] @$s5cdecl8broccoli{{[_0-9a-zA-Z]*}}F
// FIXME should it be `sil hidden`?
@cdecl(cauliflower)
public func broccoli(_ x: Int) -> Int {
return x
}

// CHECK-LABEL: sil private [thunk] [ossa] @collard_greens : $@convention(c)
// CHECK: function_ref @$s5cdecl4kale[[PRIVATE:.*]]
// CHECK: sil private [ossa] @$s5cdecl4kale[[PRIVATE:.*]]
@cdecl(collard_greens)
private func kale(_ x: Int) -> Int {
return x
}

// CHECK-LABEL: sil private [thunk] [ossa] @defaultName : $@convention(c)
// CHECK: function_ref @$s5cdecl11defaultName[[PRIVATE:.*]]
// CHECK: sil private [ossa] @$s5cdecl11defaultName[[PRIVATE:.*]]
@cdecl
private func defaultName(_ x: Int) -> Int {
return x
}
21 changes: 18 additions & 3 deletions test/attr/attr_cdecl_official.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@

// REQUIRES: swift_feature_CDecl

@cdecl("cdecl_foo") func foo(x: Int) -> Int { return x }
@cdecl(cdecl_foo) func foo(x: Int) -> Int { return x }

@cdecl("") // expected-error{{@cdecl symbol name cannot be empty}}
func emptyName(x: Int) -> Int { return x }
@cdecl(not an identifier) func invalidName() {}
// expected-error @-1 {{expected ')' in 'cdecl' attribute}}
// expected-error @-2 {{expected declaration}}

@cdecl() func emptyParen() {}
// expected-error @-1 {{expected C identifier in 'cdecl' attribute}}
// expected-error @-2 {{expected declaration}}

@cdecl(42) func aNumber() {}
// expected-error @-1 {{expected C identifier in 'cdecl' attribute}}
// expected-error @-2 {{expected declaration}}

@cdecl(a:selector:) func selectordName() {}
// expected-error @-1 {{expected ')' in 'cdecl' attribute}}
// expected-error @-2 {{expected declaration}}

@cdecl func defaultName() {}

@cdecl("noBody")
func noBody(x: Int) -> Int // expected-error{{expected '{' in body of function}}
Expand Down