Skip to content

Commit 7f6b681

Browse files
committed
[Parser] Parse custom attributes on types.
Extend the parsing of custom attributes to apply to types. Improve the lookahead for the arguments so we don't arbitrarily consume the parameter list of a function type as an attribute argument, or consume a tuple type as the attribute argument. Doesn't actually change behavior, because after parsing the custom attribute on a type, we reject it as an unknown attribute.
1 parent 5499235 commit 7f6b681

File tree

4 files changed

+170
-94
lines changed

4 files changed

+170
-94
lines changed

include/swift/Parse/Parser.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,8 +1061,23 @@ class Parser {
10611061

10621062
/// Parse a specific attribute.
10631063
ParserStatus parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
1064+
PatternBindingInitializer *&initContext,
10641065
bool isFromClangAttribute = false);
10651066

1067+
bool isCustomAttributeArgument();
1068+
bool canParseCustomAttribute();
1069+
1070+
/// Parse a custom attribute after the initial '@'.
1071+
///
1072+
/// \param atLoc The location of the already-parsed '@'.
1073+
///
1074+
/// \param initContext A reference to the initializer context used
1075+
/// for the set of custom attributes. This should start as nullptr, and
1076+
/// will get filled in by this function. The same variable should be provided
1077+
/// for every custom attribute within the same attribute list.
1078+
ParserResult<CustomAttr> parseCustomAttribute(
1079+
SourceLoc atLoc, PatternBindingInitializer *&initContext);
1080+
10661081
bool parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
10671082
DeclAttrKind DK,
10681083
bool isFromClangAttribute = false);
@@ -1090,6 +1105,7 @@ class Parser {
10901105
TypeAttributes::Convention &convention);
10911106

10921107
bool parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
1108+
PatternBindingInitializer *&initContext,
10931109
bool justChecking = false);
10941110

10951111

lib/ClangImporter/ImportDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8028,6 +8028,7 @@ void ClangImporter::Implementation::importAttributes(
80288028

80298029
// Scan through Clang attributes and map them onto Swift
80308030
// equivalents.
8031+
PatternBindingInitializer *initContext = nullptr;
80318032
bool AnyUnavailable = MappedDecl->getAttrs().isUnavailable(C);
80328033
for (clang::NamedDecl::attr_iterator AI = ClangDecl->attr_begin(),
80338034
AE = ClangDecl->attr_end(); AI != AE; ++AI) {
@@ -8207,7 +8208,8 @@ void ClangImporter::Implementation::importAttributes(
82078208
SourceLoc atLoc;
82088209
if (parser.consumeIf(tok::at_sign, atLoc)) {
82098210
(void)parser.parseDeclAttribute(
8210-
MappedDecl->getAttrs(), atLoc, /*isFromClangAttribute=*/true);
8211+
MappedDecl->getAttrs(), atLoc, initContext,
8212+
/*isFromClangAttribute=*/true);
82118213
} else {
82128214
// Complain about the missing '@'.
82138215
auto &clangSrcMgr = getClangASTContext().getSourceManager();

lib/Parse/ParseDecl.cpp

Lines changed: 149 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2819,6 +2819,101 @@ static PatternBindingInitializer *findAttributeInitContent(
28192819
return nullptr;
28202820
}
28212821

2822+
bool Parser::isCustomAttributeArgument() {
2823+
BacktrackingScope backtrack(*this);
2824+
skipSingle();
2825+
2826+
// If we have any keyword, identifier, or token that follows a function
2827+
// type's parameter list, this is a parameter list and not an attribute.
2828+
// Alternatively, we might have a token that illustrates we're not going to
2829+
// get anything following the attribute, which means the parentheses describe
2830+
// what follows the attribute.
2831+
return !Tok.isAny(
2832+
tok::arrow, tok::kw_throw, tok::kw_throws, tok::kw_rethrows,
2833+
tok::r_paren, tok::r_brace, tok::r_square, tok::r_angle) &&
2834+
!Tok.isContextualKeyword("async") && !Tok.isContextualKeyword("reasync") ;
2835+
}
2836+
2837+
bool Parser::canParseCustomAttribute() {
2838+
if (!canParseType())
2839+
return false;
2840+
2841+
if (Tok.isFollowingLParen() && isCustomAttributeArgument())
2842+
skipSingle();
2843+
2844+
return true;
2845+
}
2846+
2847+
ParserResult<CustomAttr> Parser::parseCustomAttribute(
2848+
SourceLoc atLoc, PatternBindingInitializer *&initContext) {
2849+
SyntaxContext->setCreateSyntax(SyntaxKind::CustomAttribute);
2850+
2851+
// Parse a custom attribute.
2852+
auto type = parseType(diag::expected_type);
2853+
if (type.hasCodeCompletion() || type.isNull()) {
2854+
if (Tok.is(tok::l_paren) && isCustomAttributeArgument())
2855+
skipSingle();
2856+
2857+
return ParserResult<CustomAttr>(ParserStatus(type));
2858+
}
2859+
2860+
// Parse the optional arguments.
2861+
SourceLoc lParenLoc, rParenLoc;
2862+
SmallVector<Expr *, 2> args;
2863+
SmallVector<Identifier, 2> argLabels;
2864+
SmallVector<SourceLoc, 2> argLabelLocs;
2865+
SmallVector<TrailingClosure, 2> trailingClosures;
2866+
bool hasInitializer = false;
2867+
2868+
// If we're not in a local context, we'll need a context to parse
2869+
// initializers into (should we have one). This happens for properties
2870+
// and global variables in libraries.
2871+
ParserStatus status;
2872+
if (Tok.isFollowingLParen() && isCustomAttributeArgument()) {
2873+
if (peekToken().is(tok::code_complete)) {
2874+
consumeToken(tok::l_paren);
2875+
if (CodeCompletion) {
2876+
auto typeE = new (Context) TypeExpr(type.get());
2877+
auto CCE = new (Context) CodeCompletionExpr(Tok.getLoc());
2878+
CodeCompletion->completePostfixExprParen(typeE, CCE);
2879+
}
2880+
consumeToken(tok::code_complete);
2881+
skipUntil(tok::r_paren);
2882+
consumeIf(tok::r_paren);
2883+
status.setHasCodeCompletionAndIsError();
2884+
} else {
2885+
// If we have no local context to parse the initial value into, create
2886+
// one for the PBD we'll eventually create. This allows us to have
2887+
// reasonable DeclContexts for any closures that may live inside of
2888+
// initializers.
2889+
Optional<ParseFunctionBody> initParser;
2890+
if (!CurDeclContext->isLocalContext()) {
2891+
if (!initContext) {
2892+
initContext =
2893+
new (Context) PatternBindingInitializer(CurDeclContext);
2894+
}
2895+
2896+
initParser.emplace(*this, initContext);
2897+
}
2898+
status |= parseExprList(tok::l_paren, tok::r_paren,
2899+
/*isPostfix=*/false, /*isExprBasic=*/true,
2900+
lParenLoc, args, argLabels, argLabelLocs,
2901+
rParenLoc,
2902+
trailingClosures,
2903+
SyntaxKind::TupleExprElementList);
2904+
assert(trailingClosures.empty() && "Cannot parse a trailing closure here");
2905+
hasInitializer = true;
2906+
}
2907+
}
2908+
2909+
// Form the attribute.
2910+
auto *TE = new (Context) TypeExpr(type.get());
2911+
auto customAttr = CustomAttr::create(Context, atLoc, TE, hasInitializer,
2912+
initContext, lParenLoc, args, argLabels,
2913+
argLabelLocs, rParenLoc);
2914+
return makeParserResult(status, customAttr);
2915+
}
2916+
28222917
/// \verbatim
28232918
/// attribute:
28242919
/// '_silgen_name' '(' identifier ')'
@@ -2843,7 +2938,9 @@ static PatternBindingInitializer *findAttributeInitContent(
28432938
/// but rejected since they have context-sensitive keywords.
28442939
///
28452940
ParserStatus Parser::parseDeclAttribute(
2846-
DeclAttributes &Attributes, SourceLoc AtLoc, bool isFromClangAttribute) {
2941+
DeclAttributes &Attributes, SourceLoc AtLoc,
2942+
PatternBindingInitializer *&initContext,
2943+
bool isFromClangAttribute) {
28472944
// If this not an identifier, the attribute is malformed.
28482945
if (Tok.isNot(tok::identifier) &&
28492946
Tok.isNot(tok::kw_in) &&
@@ -2963,74 +3060,11 @@ ParserStatus Parser::parseDeclAttribute(
29633060
diagnose(Tok, diag::unknown_attribute, "unknown");
29643061
} else {
29653062
// Change the context to create a custom attribute syntax.
2966-
SyntaxContext->setCreateSyntax(SyntaxKind::CustomAttribute);
2967-
// Parse a custom attribute.
2968-
auto type = parseType(diag::expected_type);
2969-
if (type.hasCodeCompletion() || type.isNull()) {
2970-
if (Tok.is(tok::l_paren))
2971-
skipSingle();
2972-
2973-
return ParserStatus(type);
2974-
}
2975-
2976-
// Parse the optional arguments.
2977-
SourceLoc lParenLoc, rParenLoc;
2978-
SmallVector<Expr *, 2> args;
2979-
SmallVector<Identifier, 2> argLabels;
2980-
SmallVector<SourceLoc, 2> argLabelLocs;
2981-
SmallVector<TrailingClosure, 2> trailingClosures;
2982-
bool hasInitializer = false;
2983-
ParserStatus status;
2984-
2985-
// If we're not in a local context, we'll need a context to parse
2986-
// initializers into (should we have one). This happens for properties
2987-
// and global variables in libraries.
2988-
PatternBindingInitializer *initContext = nullptr;
2989-
2990-
if (Tok.isFollowingLParen()) {
2991-
if (peekToken().is(tok::code_complete)) {
2992-
consumeToken(tok::l_paren);
2993-
if (CodeCompletion) {
2994-
auto typeE = new (Context) TypeExpr(type.get());
2995-
auto CCE = new (Context) CodeCompletionExpr(Tok.getLoc());
2996-
CodeCompletion->completePostfixExprParen(typeE, CCE);
2997-
}
2998-
consumeToken(tok::code_complete);
2999-
skipUntil(tok::r_paren);
3000-
consumeIf(tok::r_paren);
3001-
status.setHasCodeCompletionAndIsError();
3002-
} else {
3003-
// If we have no local context to parse the initial value into, create
3004-
// one for the PBD we'll eventually create. This allows us to have
3005-
// reasonable DeclContexts for any closures that may live inside of
3006-
// initializers.
3007-
Optional<ParseFunctionBody> initParser;
3008-
if (!CurDeclContext->isLocalContext()) {
3009-
initContext = findAttributeInitContent(Attributes);
3010-
if (!initContext)
3011-
initContext =
3012-
new (Context) PatternBindingInitializer(CurDeclContext);
3013-
3014-
initParser.emplace(*this, initContext);
3015-
}
3016-
status |= parseExprList(tok::l_paren, tok::r_paren,
3017-
/*isPostfix=*/false, /*isExprBasic=*/true,
3018-
lParenLoc, args, argLabels, argLabelLocs,
3019-
rParenLoc,
3020-
trailingClosures,
3021-
SyntaxKind::TupleExprElementList);
3022-
assert(trailingClosures.empty() && "Cannot parse a trailing closure here");
3023-
hasInitializer = true;
3024-
}
3025-
}
3063+
auto customAttr = parseCustomAttribute(AtLoc, initContext);
3064+
if (auto attr = customAttr.getPtrOrNull())
3065+
Attributes.add(attr);
30263066

3027-
// Form the attribute.
3028-
auto *TE = new (Context) TypeExpr(type.get());
3029-
auto attr = CustomAttr::create(Context, AtLoc, TE, hasInitializer,
3030-
initContext, lParenLoc, args, argLabels,
3031-
argLabelLocs, rParenLoc);
3032-
Attributes.add(attr);
3033-
return status;
3067+
return ParserStatus(customAttr);
30343068
}
30353069

30363070
// Recover by eating @foo(...) when foo is not known.
@@ -3043,7 +3077,8 @@ ParserStatus Parser::parseDeclAttribute(
30433077

30443078
bool Parser::canParseTypeAttribute() {
30453079
TypeAttributes attrs; // ignored
3046-
return !parseTypeAttribute(attrs, /*atLoc=*/SourceLoc(),
3080+
PatternBindingInitializer *initContext = nullptr;
3081+
return !parseTypeAttribute(attrs, /*atLoc=*/SourceLoc(), initContext,
30473082
/*justChecking*/ true);
30483083
}
30493084

@@ -3204,6 +3239,7 @@ bool Parser::parseConventionAttributeInternal(
32043239
/// canParseTypeAttribute; don't emit any diagnostics, and there's
32053240
/// no need to actually record the attribute
32063241
bool Parser::parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
3242+
PatternBindingInitializer *&initContext,
32073243
bool justChecking) {
32083244
// If this not an identifier, the attribute is malformed.
32093245
if (Tok.isNot(tok::identifier) &&
@@ -3218,15 +3254,11 @@ bool Parser::parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
32183254
TypeAttrKind attr = TypeAttributes::getAttrKindFromString(Tok.getText());
32193255

32203256
if (attr == TAK_Count) {
3221-
if (justChecking) return true;
3222-
32233257
auto declAttrID = DeclAttribute::getAttrKindFromString(Tok.getText());
3224-
if (declAttrID == DAK_Count) {
3225-
// Not a decl or type attribute.
3226-
diagnose(Tok, diag::unknown_attribute, Tok.getText());
3227-
} else {
3228-
// Otherwise this is a valid decl attribute so they should have put it on
3229-
// the decl instead of the type.
3258+
if (declAttrID != DAK_Count) {
3259+
// This is a valid decl attribute so they should have put it on the decl
3260+
// instead of the type.
3261+
if (justChecking) return true;
32303262

32313263
// If this is the first attribute, and if we are on a simple decl, emit a
32323264
// fixit to move the attribute. Otherwise, we don't have the location of
@@ -3246,21 +3278,45 @@ bool Parser::parseTypeAttribute(TypeAttributes &Attributes, SourceLoc AtLoc,
32463278
.fixItInsert(StructureMarkers.back().Loc,
32473279
"@" + Tok.getText().str()+" ");
32483280
}
3281+
3282+
// Recover by eating @foo(...) when foo is not known.
3283+
consumeToken();
3284+
SyntaxParsingContext TokListContext(SyntaxContext, SyntaxKind::TokenList);
3285+
3286+
if (Tok.is(tok::l_paren) && getEndOfPreviousLoc() == Tok.getLoc()) {
3287+
CancellableBacktrackingScope backtrack(*this);
3288+
skipSingle();
3289+
// If we found '->', or 'throws' after paren, it's likely a parameter
3290+
// of function type.
3291+
if (Tok.isNot(tok::arrow, tok::kw_throws, tok::kw_rethrows,
3292+
tok::kw_throw))
3293+
backtrack.cancelBacktrack();
3294+
}
3295+
3296+
return true;
32493297
}
3250-
3251-
// Recover by eating @foo(...) when foo is not known.
3252-
consumeToken();
3253-
SyntaxParsingContext TokListContext(SyntaxContext, SyntaxKind::TokenList);
32543298

3255-
if (Tok.is(tok::l_paren) && getEndOfPreviousLoc() == Tok.getLoc()) {
3256-
CancellableBacktrackingScope backtrack(*this);
3257-
skipSingle();
3258-
// If we found '->', or 'throws' after paren, it's likely a parameter
3259-
// of function type.
3260-
if (Tok.isNot(tok::arrow, tok::kw_throws, tok::kw_rethrows,
3261-
tok::kw_throw))
3262-
backtrack.cancelBacktrack();
3299+
// If we're just checking, try to parse now.
3300+
if (justChecking)
3301+
return !canParseCustomAttribute();
3302+
3303+
// Parse as a custom attribute.
3304+
auto customAttrResult = parseCustomAttribute(AtLoc, initContext);
3305+
if (customAttrResult.isParseErrorOrHasCompletion())
3306+
return true;
3307+
3308+
// Diagnose the attribute, because we don't yet handle custom type
3309+
// attributes.
3310+
std::string typeName;
3311+
auto customAttr = customAttrResult.get();
3312+
if (auto typeRepr = customAttr->getTypeRepr()) {
3313+
llvm::raw_string_ostream out(typeName);
3314+
typeRepr->print(out);
3315+
} else {
3316+
typeName = customAttr->getType().getString();
32633317
}
3318+
3319+
diagnose(customAttr->getLocation(), diag::unknown_attribute, typeName);
32643320
return true;
32653321
}
32663322

@@ -3447,12 +3503,13 @@ ParserStatus Parser::parseDeclAttributeList(DeclAttributes &Attributes) {
34473503
if (Tok.isNot(tok::at_sign))
34483504
return makeParserSuccess();
34493505

3506+
PatternBindingInitializer *initContext = nullptr;
34503507
ParserStatus Status;
34513508
SyntaxParsingContext AttrListCtx(SyntaxContext, SyntaxKind::AttributeList);
34523509
do {
34533510
SyntaxParsingContext AttrCtx(SyntaxContext, SyntaxKind::Attribute);
34543511
SourceLoc AtLoc = consumeToken();
3455-
Status |= parseDeclAttribute(Attributes, AtLoc);
3512+
Status |= parseDeclAttribute(Attributes, AtLoc, initContext);
34563513
} while (Tok.is(tok::at_sign));
34573514
return Status;
34583515
}
@@ -3669,6 +3726,7 @@ bool Parser::parseDeclModifierList(DeclAttributes &Attributes,
36693726
bool Parser::parseTypeAttributeListPresent(ParamDecl::Specifier &Specifier,
36703727
SourceLoc &SpecifierLoc,
36713728
TypeAttributes &Attributes) {
3729+
PatternBindingInitializer *initContext = nullptr;
36723730
Specifier = ParamDecl::Specifier::Default;
36733731
while (Tok.is(tok::kw_inout) ||
36743732
(Tok.is(tok::identifier) &&
@@ -3701,7 +3759,7 @@ bool Parser::parseTypeAttributeListPresent(ParamDecl::Specifier &Specifier,
37013759
Attributes.AtLoc = Tok.getLoc();
37023760
SyntaxParsingContext AttrCtx(SyntaxContext, SyntaxKind::Attribute);
37033761
SourceLoc AtLoc = consumeToken();
3704-
if (parseTypeAttribute(Attributes, AtLoc))
3762+
if (parseTypeAttribute(Attributes, AtLoc, initContext))
37053763
return true;
37063764
}
37073765

test/attr/attributes.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ func func_with_unknown_attr2(x: @unknown(_) Int) {} // expected-error {{unknown
205205
func func_with_unknown_attr3(x: @unknown(Int) -> Int) {} // expected-error {{unknown attribute 'unknown'}}
206206
func func_with_unknown_attr4(x: @unknown(Int) throws -> Int) {} // expected-error {{unknown attribute 'unknown'}}
207207
func func_with_unknown_attr5(x: @unknown (x: Int, y: Int)) {} // expected-error {{unknown attribute 'unknown'}}
208-
func func_with_unknown_attr6(x: @unknown(x: Int, y: Int)) {} // expected-error {{unknown attribute 'unknown'}} expected-error {{expected parameter type following ':'}}
209-
func func_with_unknown_attr7(x: @unknown (Int) () -> Int) {} // expected-error {{unknown attribute 'unknown'}} expected-error {{expected ',' separator}} {{47-47=,}} expected-error {{unnamed parameters must be written with the empty name '_'}} {{48-48=_: }}
208+
func func_with_unknown_attr6(x: @unknown(x: Int, y: Int)) {} // expected-error {{unknown attribute 'unknown'}}
209+
func func_with_unknown_attr7(x: @unknown (Int) () -> Int) {} // expected-error {{unknown attribute 'unknown'}}
210210

211211
func func_type_attribute_with_space(x: @convention (c) () -> Int) {} // OK. Known attributes can have space before its paren.
212212

0 commit comments

Comments
 (0)