Skip to content

Commit 3cc452e

Browse files
Merge pull request #28479 from varungandhi-apple/vg-allow-clang-types-in-convention-attr
Allow spelling out a C type in the convention attribute.
2 parents 07783cb + 7d297bc commit 3cc452e

File tree

10 files changed

+183
-74
lines changed

10 files changed

+183
-74
lines changed

include/swift/AST/Attr.h

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,24 @@ class TypeAttributes {
6565
/// AtLoc - This is the location of the first '@' in the attribute specifier.
6666
/// If this is an empty attribute specifier, then this will be an invalid loc.
6767
SourceLoc AtLoc;
68-
Optional<StringRef> convention = None;
69-
Optional<StringRef> conventionWitnessMethodProtocol = None;
68+
69+
struct Convention {
70+
StringRef Name = {};
71+
StringRef WitnessMethodProtocol = {};
72+
StringRef ClangType = {};
73+
// Carry the source location for diagnostics.
74+
SourceLoc ClangTypeLoc = {};
75+
76+
/// Convenience factory function to create a Swift convention.
77+
///
78+
/// Don't use this function if you are creating a C convention as you
79+
/// probably need a ClangType field as well.
80+
static Convention makeSwiftConvention(StringRef name) {
81+
return {name, "", "", {}};
82+
}
83+
};
84+
85+
Optional<Convention> ConventionArguments;
7086

7187
// Indicates whether the type's '@differentiable' attribute has a 'linear'
7288
// argument.
@@ -135,8 +151,19 @@ class TypeAttributes {
135151
return true;
136152
}
137153

138-
bool hasConvention() const { return convention.hasValue(); }
139-
StringRef getConvention() const { return *convention; }
154+
bool hasConvention() const { return ConventionArguments.hasValue(); }
155+
156+
/// Returns the primary calling convention string.
157+
///
158+
/// Note: For C conventions, this may not represent the full convention.
159+
StringRef getConventionName() const {
160+
return ConventionArguments.getValue().Name;
161+
}
162+
163+
/// Show the string enclosed between @convention(..)'s parentheses.
164+
///
165+
/// For example, @convention(foo, bar) will give the string "foo, bar".
166+
void getConventionArguments(SmallVectorImpl<char> &buffer) const;
140167

141168
bool hasOwnership() const {
142169
return getOwnership() != ReferenceOwnership::Strong;

include/swift/AST/DiagnosticsParse.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,13 @@ ERROR(convention_attribute_expected_name,none,
14301430
"expected convention name identifier in 'convention' attribute", ())
14311431
ERROR(convention_attribute_expected_rparen,none,
14321432
"expected ')' after convention name for 'convention' attribute", ())
1433+
ERROR(convention_attribute_ctype_expected_label,none,
1434+
"expected 'cType' label in 'convention' attribute", ())
1435+
ERROR(convention_attribute_ctype_expected_colon,none,
1436+
"expected ':' after 'cType' for 'convention' attribute", ())
1437+
ERROR(convention_attribute_ctype_expected_string,none,
1438+
"expected string literal containing clang type for 'cType' in "
1439+
"'convention' attribute", ())
14331440
ERROR(convention_attribute_witness_method_expected_colon,none,
14341441
"expected ':' after 'witness_method' for 'convention' attribute", ())
14351442
ERROR(convention_attribute_witness_method_expected_protocol,none,

include/swift/Parse/Parser.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,10 @@ class Parser {
10211021
bool parseTypeAttributeListPresent(ParamDecl::Specifier &Specifier,
10221022
SourceLoc &SpecifierLoc,
10231023
TypeAttributes &Attributes);
1024+
1025+
bool parseConventionAttributeInternal(bool justChecking,
1026+
TypeAttributes::Convention &convention);
1027+
10241028
bool parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
10251029
bool justChecking = false);
10261030

lib/AST/Attr.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "swift/AST/TypeRepr.h"
2727
#include "swift/AST/Types.h"
2828
#include "swift/Basic/Defer.h"
29+
#include "swift/Basic/QuotedString.h"
2930
#include "llvm/ADT/SmallString.h"
3031
#include "llvm/ADT/StringSwitch.h"
3132
#include "llvm/Support/ErrorHandling.h"
@@ -83,6 +84,18 @@ StringRef swift::getAccessLevelSpelling(AccessLevel value) {
8384
llvm_unreachable("Unhandled AccessLevel in switch.");
8485
}
8586

87+
void TypeAttributes::getConventionArguments(SmallVectorImpl<char> &buf) const {
88+
llvm::raw_svector_ostream stream(buf);
89+
auto &convention = ConventionArguments.getValue();
90+
stream << convention.Name;
91+
if (!convention.WitnessMethodProtocol.empty()) {
92+
stream << ": " << convention.WitnessMethodProtocol;
93+
return;
94+
}
95+
if (!convention.ClangType.empty())
96+
stream << ", cType: " << QuotedString(convention.ClangType);
97+
}
98+
8699
/// Given a name like "autoclosure", return the type attribute ID that
87100
/// corresponds to it. This returns TAK_Count on failure.
88101
///

lib/AST/TypeRepr.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,12 @@ void AttributedTypeRepr::printAttrs(ASTPrinter &Printer,
312312
if (hasAttr(TAK_thick))
313313
Printer.printSimpleAttr("@thick") << " ";
314314

315-
if (hasAttr(TAK_convention) && Attrs.convention.hasValue()) {
315+
if (hasAttr(TAK_convention) && Attrs.hasConvention()) {
316316
Printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute);
317317
Printer.printAttrName("@convention");
318-
Printer << "(" << Attrs.convention.getValue() << ")";
318+
SmallString<32> convention;
319+
Attrs.getConventionArguments(convention);
320+
Printer << "(" << convention << ")";
319321
Printer.printStructurePost(PrintStructureKind::BuiltinAttribute);
320322
Printer << " ";
321323
}

lib/Parse/ParseDecl.cpp

Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,6 +2370,91 @@ static bool parseDifferentiableAttributeArgument(Parser &P,
23702370
return false;
23712371
}
23722372

2373+
/// Parse the inside of a convention attribute '(...)'.
2374+
///
2375+
/// The '@convention' prefix should've been parsed by the caller.
2376+
/// See `Parser::parseTypeAttribute` for the justChecking argument.
2377+
///
2378+
/// Returns true if there was an error.
2379+
bool Parser::parseConventionAttributeInternal(
2380+
bool justChecking, TypeAttributes::Convention &convention) {
2381+
SourceLoc LPLoc;
2382+
if (!consumeIfNotAtStartOfLine(tok::l_paren)) {
2383+
if (!justChecking)
2384+
diagnose(Tok, diag::convention_attribute_expected_lparen);
2385+
return true;
2386+
}
2387+
2388+
if (Tok.isNot(tok::identifier)) {
2389+
if (!justChecking)
2390+
diagnose(Tok, diag::convention_attribute_expected_name);
2391+
return true;
2392+
}
2393+
2394+
convention.Name = Tok.getText();
2395+
consumeToken(tok::identifier);
2396+
2397+
// Consume extra (optional) ', cType: " blah blah "'
2398+
if (consumeIf(tok::comma)) {
2399+
if (Tok.isNot(tok::identifier)) {
2400+
if (!justChecking)
2401+
diagnose(Tok, diag::convention_attribute_ctype_expected_label);
2402+
return true;
2403+
}
2404+
auto cTypeLabel = Tok.getText();
2405+
consumeToken(tok::identifier);
2406+
if (cTypeLabel != "cType") {
2407+
if (!justChecking)
2408+
diagnose(Tok, diag::convention_attribute_ctype_expected_label);
2409+
return true;
2410+
}
2411+
if (!consumeIf(tok::colon)) {
2412+
if (!justChecking)
2413+
diagnose(Tok, diag::convention_attribute_ctype_expected_colon);
2414+
return true;
2415+
}
2416+
if (Tok.isNot(tok::string_literal)) {
2417+
if (!justChecking)
2418+
diagnose(Tok, diag::convention_attribute_ctype_expected_string);
2419+
return true;
2420+
}
2421+
if (auto ty = getStringLiteralIfNotInterpolated(Tok.getLoc(), "(C type)")) {
2422+
convention.ClangType = ty.getValue();
2423+
convention.ClangTypeLoc = Tok.getLoc();
2424+
}
2425+
consumeToken(tok::string_literal);
2426+
}
2427+
2428+
if (convention.Name == "witness_method") {
2429+
if (!consumeIf(tok::colon)) {
2430+
if (!justChecking)
2431+
diagnose(Tok,
2432+
diag::convention_attribute_witness_method_expected_colon);
2433+
return true;
2434+
}
2435+
if (Tok.isNot(tok::identifier)) {
2436+
if (!justChecking)
2437+
diagnose(Tok,
2438+
diag::convention_attribute_witness_method_expected_protocol);
2439+
return true;
2440+
}
2441+
2442+
convention.WitnessMethodProtocol = Tok.getText();
2443+
consumeToken(tok::identifier);
2444+
}
2445+
2446+
// Parse the ')'. We can't use parseMatchingToken if we're in
2447+
// just-checking mode.
2448+
if (justChecking && Tok.isNot(tok::r_paren))
2449+
return true;
2450+
2451+
SourceLoc RPLoc;
2452+
parseMatchingToken(tok::r_paren, RPLoc,
2453+
diag::convention_attribute_expected_rparen,
2454+
LPLoc);
2455+
return false;
2456+
}
2457+
23732458
/// \verbatim
23742459
/// attribute-type:
23752460
/// 'noreturn'
@@ -2443,57 +2528,17 @@ bool Parser::parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
24432528
StringRef Text = Tok.getText();
24442529
consumeToken();
24452530

2446-
StringRef conventionName;
2447-
StringRef witnessMethodProtocol;
2448-
2531+
TypeAttributes::Convention convention;
24492532
if (attr == TAK_convention) {
2450-
SourceLoc LPLoc;
2451-
if (!consumeIfNotAtStartOfLine(tok::l_paren)) {
2452-
if (!justChecking)
2453-
diagnose(Tok, diag::convention_attribute_expected_lparen);
2454-
return true;
2455-
}
2456-
2457-
if (Tok.isNot(tok::identifier)) {
2458-
if (!justChecking)
2459-
diagnose(Tok, diag::convention_attribute_expected_name);
2533+
bool failedToParse =
2534+
parseConventionAttributeInternal(justChecking, convention);
2535+
if (failedToParse) {
2536+
if (Tok.is(tok::r_paren))
2537+
consumeToken();
24602538
return true;
24612539
}
2462-
2463-
conventionName = Tok.getText();
2464-
consumeToken(tok::identifier);
2465-
2466-
if (conventionName == "witness_method") {
2467-
if (Tok.isNot(tok::colon)) {
2468-
if (!justChecking)
2469-
diagnose(Tok,
2470-
diag::convention_attribute_witness_method_expected_colon);
2471-
return true;
2472-
}
2473-
consumeToken(tok::colon);
2474-
if (Tok.isNot(tok::identifier)) {
2475-
if (!justChecking)
2476-
diagnose(Tok,
2477-
diag::convention_attribute_witness_method_expected_protocol);
2478-
return true;
2479-
}
2480-
2481-
witnessMethodProtocol = Tok.getText();
2482-
consumeToken(tok::identifier);
2483-
}
2484-
2485-
// Parse the ')'. We can't use parseMatchingToken if we're in
2486-
// just-checking mode.
2487-
if (justChecking && Tok.isNot(tok::r_paren))
2488-
return true;
2489-
2490-
SourceLoc RPLoc;
2491-
parseMatchingToken(tok::r_paren, RPLoc,
2492-
diag::convention_attribute_expected_rparen,
2493-
LPLoc);
24942540
}
24952541

2496-
24972542
// In just-checking mode, we only need to consume the tokens, and we don't
24982543
// want to do any other analysis.
24992544
if (justChecking)
@@ -2592,8 +2637,7 @@ bool Parser::parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
25922637

25932638
// Convention attribute.
25942639
case TAK_convention:
2595-
Attributes.convention = conventionName;
2596-
Attributes.conventionWitnessMethodProtocol = witnessMethodProtocol;
2640+
Attributes.ConventionArguments = convention;
25972641
break;
25982642

25992643
case TAK__opaqueReturnTypeOf: {

lib/ParseSIL/ParseSIL.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,8 @@ bool SILParser::parseSILType(SILType &Result,
12721272
if (IsFuncDecl && !attrs.has(TAK_convention)) {
12731273
// Use a random location.
12741274
attrs.setAttr(TAK_convention, P.PreviousLoc);
1275-
attrs.convention = "thin";
1275+
attrs.ConventionArguments =
1276+
TypeAttributes::Convention::makeSwiftConvention("thin");
12761277
}
12771278

12781279
ParserResult<TypeRepr> TyR = P.parseType(diag::expected_sil_type,

lib/Sema/TypeCheckType.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,7 +2168,7 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
21682168
if (!attrs.hasConvention()) {
21692169
rep = SILFunctionType::Representation::Thick;
21702170
} else {
2171-
auto convention = attrs.getConvention();
2171+
auto convention = attrs.getConventionName();
21722172
// SIL exposes a greater number of conventions than Swift source.
21732173
auto parsedRep =
21742174
llvm::StringSwitch<Optional<SILFunctionType::Representation>>(
@@ -2185,14 +2185,15 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
21852185
.Default(None);
21862186
if (!parsedRep) {
21872187
diagnose(attrs.getLoc(TAK_convention),
2188-
diag::unsupported_sil_convention, attrs.getConvention());
2188+
diag::unsupported_sil_convention, attrs.getConventionName());
21892189
rep = SILFunctionType::Representation::Thin;
21902190
} else {
21912191
rep = *parsedRep;
21922192
}
21932193

21942194
if (rep == SILFunctionType::Representation::WitnessMethod) {
2195-
auto protocolName = *attrs.conventionWitnessMethodProtocol;
2195+
auto protocolName =
2196+
attrs.ConventionArguments.getValue().WitnessMethodProtocol;
21962197
witnessMethodProtocol = new (Context) SimpleIdentTypeRepr(
21972198
SourceLoc(), Context.getIdentifier(protocolName));
21982199
}
@@ -2225,15 +2226,15 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
22252226
if (attrs.hasConvention()) {
22262227
auto parsedRep =
22272228
llvm::StringSwitch<Optional<FunctionType::Representation>>(
2228-
attrs.getConvention())
2229+
attrs.getConventionName())
22292230
.Case("swift", FunctionType::Representation::Swift)
22302231
.Case("block", FunctionType::Representation::Block)
22312232
.Case("thin", FunctionType::Representation::Thin)
22322233
.Case("c", FunctionType::Representation::CFunctionPointer)
22332234
.Default(None);
22342235
if (!parsedRep) {
22352236
diagnose(attrs.getLoc(TAK_convention), diag::unsupported_convention,
2236-
attrs.getConvention());
2237+
attrs.getConventionName());
22372238
rep = FunctionType::Representation::Swift;
22382239
} else {
22392240
rep = *parsedRep;
@@ -2244,7 +2245,7 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
22442245
rep == FunctionType::Representation::Block) {
22452246
diagnose(attrs.getLoc(TAK_convention),
22462247
diag::invalid_autoclosure_and_convention_attributes,
2247-
attrs.getConvention());
2248+
attrs.getConventionName());
22482249
attrs.clearAttribute(TAK_convention);
22492250
}
22502251
}
@@ -2357,7 +2358,7 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
23572358
// Remove the function attributes from the set so that we don't diagnose.
23582359
for (auto i : FunctionAttrs)
23592360
attrs.clearAttribute(i);
2360-
attrs.convention = None;
2361+
attrs.ConventionArguments = None;
23612362
}
23622363

23632364
// In SIL, handle @opened (n), which creates an existential archetype.

test/Parse/c_function_pointers.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,11 @@ if true {
4747
func genericFunc<T>(_ t: T) -> T { return t }
4848

4949
let f: @convention(c) (Int) -> Int = genericFunc // expected-error{{cannot be formed from a reference to a generic function}}
50+
51+
func ct1() -> () { print("") }
52+
53+
let ct1ref0 : @convention(c, cType: "void *(void)") () -> () = ct1
54+
let ct1ref1 : @convention(c, cType: "void *(void)") = ct1 // expected-error{{expected type}}
55+
let ct1ref2 : @convention(c, ) () -> () = ct1 // expected-error{{expected 'cType' label in 'convention' attribute}}
56+
let ct1ref3 : @convention(c, cType) () -> () = ct1 // expected-error{{expected ':' after 'cType' for 'convention' attribute}}
57+
let ct1ref4 : @convention(c, cType: ) () -> () = ct1 // expected-error{{expected string literal containing clang type for 'cType' in 'convention' attribute}}

0 commit comments

Comments
 (0)