Skip to content

Commit

Permalink
Introduce an 'external_source_symbol' attribute that describes the or…
Browse files Browse the repository at this point in the history
…igin

and the nature of a declaration

This commit adds an external_source_symbol attribute to Clang. This attribute
specifies that a declaration originates from an external source and describes
the nature of that source. This attribute will be used to improve IDE features
like 'jump-to-definition' for mixed-language projects or project that use
auto-generated code.

rdar://30423368

Differential Revision: https://reviews.llvm.org/D29819

llvm-svn: 296649
  • Loading branch information
hyp committed Mar 1, 2017
1 parent ceccf2d commit d5d27e1
Show file tree
Hide file tree
Showing 15 changed files with 430 additions and 26 deletions.
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -527,6 +527,17 @@ def Availability : InheritableAttr {
let Documentation = [AvailabilityDocs];
}

def ExternalSourceSymbol : InheritableAttr {
let Spellings = [GNU<"external_source_symbol">,
CXX11<"clang", "external_source_symbol">];
let Args = [StringArgument<"language", 1>,
StringArgument<"definedIn", 1>,
BoolArgument<"generatedDeclaration", 1>];
let HasCustomParsing = 1;
// let Subjects = SubjectList<[Named]>;
let Documentation = [ExternalSourceSymbolDocs];
}

def Blocks : InheritableAttr {
let Spellings = [GNU<"blocks">];
let Args = [EnumArgument<"Type", "BlockType", ["byref"], ["ByRef"]>];
Expand Down
57 changes: 57 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -960,6 +960,63 @@ When one method overrides another, the overriding method can be more widely avai
}];
}

def ExternalSourceSymbolDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``external_source_symbol`` attribute specifies that a declaration originates
from an external source and describes the nature of that source.

The fact that Clang is capable of recognizing declarations that were defined
externally can be used to provide better tooling support for mixed-language
projects or projects that rely on auto-generated code. For instance, an IDE that
uses Clang and that supports mixed-language projects can use this attribute to
provide a correct 'jump-to-definition' feature. For a concrete example,
consider a protocol that's defined in a Swift file:

.. code-block:: swift

@objc public protocol SwiftProtocol {
func method()
}

This protocol can be used from Objective-C code by including a header file that
was generated by the Swift compiler. The declarations in that header can use
the ``external_source_symbol`` attribute to make Clang aware of the fact
that ``SwiftProtocol`` actually originates from a Swift module:

.. code-block:: objc

__attribute__((external_source_symbol(language=Swift,defined_in="module")))
@protocol SwiftProtocol
@required
- (void) method;
@end

Consequently, when 'jump-to-definition' is performed at a location that
references ``SwiftProtocol``, the IDE can jump to the original definition in
the Swift source file rather than jumping to the Objective-C declaration in the
auto-generated header file.

The ``external_source_symbol`` attribute is a comma-separated list that includes
clauses that describe the origin and the nature of the particular declaration.
Those clauses can be:

language=\ *string-literal*
The name of the source language in which this declaration was defined.

defined_in=\ *string-literal*
The name of the source container in which the declaration was defined. The
exact definition of source container is language-specific, e.g. Swift's
source containers are modules, so ``defined_in`` should specify the Swift
module name.

generated_declaration
This declaration was automatically generated by some tool.

The clauses can be specified in any order. The clauses that are listed above are
all optional, but the attribute has to have at least one clause.
}];
}

def RequireConstantInitDocs : Documentation {
let Category = DocCatVariable;
Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Basic/DiagnosticCommonKinds.td
Expand Up @@ -45,7 +45,9 @@ def err_expected_colon_after_setter_name : Error<
"must end with ':'">;
def err_expected_string_literal : Error<"expected string literal "
"%select{in %1|for diagnostic message in static_assert|"
"for optional message in 'availability' attribute}0">;
"for optional message in 'availability' attribute|"
"for %select{language|source container}1 name in "
"'external_source_symbol' attribute}0">;
def err_invalid_string_udl : Error<
"string literal with user-defined suffix cannot be used here">;
def err_invalid_character_udl : Error<
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Expand Up @@ -859,6 +859,12 @@ def err_availability_query_repeated_platform: Error<
def err_availability_query_repeated_star : Error<
"'*' query has already been specified">;

// External source symbol attribute
def err_external_source_symbol_expected_keyword : Error<
"expected 'language', 'defined_in', or 'generated_declaration'">;
def err_external_source_symbol_duplicate_clause : Error<
"duplicate %0 clause in an 'external_source_symbol' attribute">;

// Type safety attributes
def err_type_safety_unknown_flag : Error<
"invalid comparison flag %0; use 'layout_compatible' or 'must_be_null'">;
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2754,7 +2754,8 @@ def warn_attribute_wrong_decl_type : Warning<
"|functions, methods, enums, and classes"
"|structs, classes, variables, functions, and inline namespaces"
"|variables, functions, methods, types, enumerations, enumerators, labels, and non-static data members"
"|classes and enumerations}1">,
"|classes and enumerations"
"|named declarations}1">,
InGroup<IgnoredAttributes>;
def err_attribute_wrong_decl_type : Error<warn_attribute_wrong_decl_type.Text>;
def warn_type_attribute_wrong_type : Warning<
Expand Down
18 changes: 18 additions & 0 deletions clang/include/clang/Parse/Parser.h
Expand Up @@ -142,6 +142,10 @@ class Parser : public CodeCompletionHandler {
/// \brief Identifier for "replacement".
IdentifierInfo *Ident_replacement;

/// Identifiers used by the 'external_source_symbol' attribute.
IdentifierInfo *Ident_language, *Ident_defined_in,
*Ident_generated_declaration;

/// C++0x contextual keywords.
mutable IdentifierInfo *Ident_final;
mutable IdentifierInfo *Ident_GNU_final;
Expand Down Expand Up @@ -2197,6 +2201,12 @@ class Parser : public CodeCompletionHandler {
Declarator *D);
IdentifierLoc *ParseIdentifierLoc();

unsigned
ParseClangAttributeArgs(IdentifierInfo *AttrName, SourceLocation AttrNameLoc,
ParsedAttributes &Attrs, SourceLocation *EndLoc,
IdentifierInfo *ScopeName, SourceLocation ScopeLoc,
AttributeList::Syntax Syntax);

void MaybeParseCXX11Attributes(Declarator &D) {
if (getLangOpts().CPlusPlus11 && isCXX11AttributeSpecifier()) {
ParsedAttributesWithRange attrs(AttrFactory);
Expand Down Expand Up @@ -2286,6 +2296,14 @@ class Parser : public CodeCompletionHandler {
Optional<AvailabilitySpec> ParseAvailabilitySpec();
ExprResult ParseAvailabilityCheckExpr(SourceLocation StartLoc);

void ParseExternalSourceSymbolAttribute(IdentifierInfo &ExternalSourceSymbol,
SourceLocation Loc,
ParsedAttributes &Attrs,
SourceLocation *EndLoc,
IdentifierInfo *ScopeName,
SourceLocation ScopeLoc,
AttributeList::Syntax Syntax);

void ParseObjCBridgeRelatedAttribute(IdentifierInfo &ObjCBridgeRelated,
SourceLocation ObjCBridgeRelatedLoc,
ParsedAttributes &attrs,
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/AttributeList.h
Expand Up @@ -927,6 +927,7 @@ enum AttributeDeclKind {
ExpectedStructClassVariableFunctionOrInlineNamespace,
ExpectedForMaybeUnused,
ExpectedEnumOrClass,
ExpectedNamedDecl,
};

} // end namespace clang
Expand Down
136 changes: 136 additions & 0 deletions clang/lib/Parse/ParseDecl.cpp
Expand Up @@ -356,6 +356,10 @@ void Parser::ParseGNUAttributeArgs(IdentifierInfo *AttrName,
ParseAvailabilityAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName,
ScopeLoc, Syntax);
return;
} else if (AttrKind == AttributeList::AT_ExternalSourceSymbol) {
ParseExternalSourceSymbolAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, Syntax);
return;
} else if (AttrKind == AttributeList::AT_ObjCBridgeRelated) {
ParseObjCBridgeRelatedAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, Syntax);
Expand Down Expand Up @@ -389,6 +393,25 @@ void Parser::ParseGNUAttributeArgs(IdentifierInfo *AttrName,
ScopeLoc, Syntax);
}

unsigned Parser::ParseClangAttributeArgs(
IdentifierInfo *AttrName, SourceLocation AttrNameLoc,
ParsedAttributes &Attrs, SourceLocation *EndLoc, IdentifierInfo *ScopeName,
SourceLocation ScopeLoc, AttributeList::Syntax Syntax) {
assert(Tok.is(tok::l_paren) && "Attribute arg list not starting with '('");

AttributeList::Kind AttrKind =
AttributeList::getKind(AttrName, ScopeName, Syntax);

if (AttrKind == AttributeList::AT_ExternalSourceSymbol) {
ParseExternalSourceSymbolAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, Syntax);
return Attrs.getList() ? Attrs.getList()->getNumArgs() : 0;
}

return ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, Syntax);
}

bool Parser::ParseMicrosoftDeclSpecArgs(IdentifierInfo *AttrName,
SourceLocation AttrNameLoc,
ParsedAttributes &Attrs) {
Expand Down Expand Up @@ -1064,6 +1087,119 @@ void Parser::ParseAvailabilityAttribute(IdentifierInfo &Availability,
Syntax, StrictLoc, ReplacementExpr.get());
}

/// \brief Parse the contents of the "external_source_symbol" attribute.
///
/// external-source-symbol-attribute:
/// 'external_source_symbol' '(' keyword-arg-list ')'
///
/// keyword-arg-list:
/// keyword-arg
/// keyword-arg ',' keyword-arg-list
///
/// keyword-arg:
/// 'language' '=' <string>
/// 'defined_in' '=' <string>
/// 'generated_declaration'
void Parser::ParseExternalSourceSymbolAttribute(
IdentifierInfo &ExternalSourceSymbol, SourceLocation Loc,
ParsedAttributes &Attrs, SourceLocation *EndLoc, IdentifierInfo *ScopeName,
SourceLocation ScopeLoc, AttributeList::Syntax Syntax) {
// Opening '('.
BalancedDelimiterTracker T(*this, tok::l_paren);
if (T.expectAndConsume())
return;

// Initialize the pointers for the keyword identifiers when required.
if (!Ident_language) {
Ident_language = PP.getIdentifierInfo("language");
Ident_defined_in = PP.getIdentifierInfo("defined_in");
Ident_generated_declaration = PP.getIdentifierInfo("generated_declaration");
}

ExprResult Language;
bool HasLanguage = false;
ExprResult DefinedInExpr;
bool HasDefinedIn = false;
IdentifierLoc *GeneratedDeclaration = nullptr;

// Parse the language/defined_in/generated_declaration keywords
do {
if (Tok.isNot(tok::identifier)) {
Diag(Tok, diag::err_external_source_symbol_expected_keyword);
SkipUntil(tok::r_paren, StopAtSemi);
return;
}

SourceLocation KeywordLoc = Tok.getLocation();
IdentifierInfo *Keyword = Tok.getIdentifierInfo();
if (Keyword == Ident_generated_declaration) {
if (GeneratedDeclaration) {
Diag(Tok, diag::err_external_source_symbol_duplicate_clause) << Keyword;
SkipUntil(tok::r_paren, StopAtSemi);
return;
}
GeneratedDeclaration = ParseIdentifierLoc();
continue;
}

if (Keyword != Ident_language && Keyword != Ident_defined_in) {
Diag(Tok, diag::err_external_source_symbol_expected_keyword);
SkipUntil(tok::r_paren, StopAtSemi);
return;
}

ConsumeToken();
if (ExpectAndConsume(tok::equal, diag::err_expected_after,
Keyword->getName())) {
SkipUntil(tok::r_paren, StopAtSemi);
return;
}

bool HadLanguage = HasLanguage, HadDefinedIn = HasDefinedIn;
if (Keyword == Ident_language)
HasLanguage = true;
else
HasDefinedIn = true;

if (Tok.isNot(tok::string_literal)) {
Diag(Tok, diag::err_expected_string_literal)
<< /*Source='external_source_symbol attribute'*/ 3
<< /*language | source container*/ (Keyword != Ident_language);
SkipUntil(tok::comma, tok::r_paren, StopAtSemi | StopBeforeMatch);
continue;
}
if (Keyword == Ident_language) {
if (HadLanguage) {
Diag(KeywordLoc, diag::err_external_source_symbol_duplicate_clause)
<< Keyword;
ParseStringLiteralExpression();
continue;
}
Language = ParseStringLiteralExpression();
} else {
assert(Keyword == Ident_defined_in && "Invalid clause keyword!");
if (HadDefinedIn) {
Diag(KeywordLoc, diag::err_external_source_symbol_duplicate_clause)
<< Keyword;
ParseStringLiteralExpression();
continue;
}
DefinedInExpr = ParseStringLiteralExpression();
}
} while (TryConsumeToken(tok::comma));

// Closing ')'.
if (T.consumeClose())
return;
if (EndLoc)
*EndLoc = T.getCloseLocation();

ArgsUnion Args[] = {Language.get(), DefinedInExpr.get(),
GeneratedDeclaration};
Attrs.addNew(&ExternalSourceSymbol, SourceRange(Loc, T.getCloseLocation()),
ScopeName, ScopeLoc, Args, llvm::array_lengthof(Args), Syntax);
}

/// \brief Parse the contents of the "objc_bridge_related" attribute.
/// objc_bridge_related '(' related_class ',' opt-class_method ',' opt-instance_method ')'
/// related_class:
Expand Down
56 changes: 32 additions & 24 deletions clang/lib/Parse/ParseDeclCXX.cpp
Expand Up @@ -3823,36 +3823,44 @@ bool Parser::ParseCXX11AttributeArgs(IdentifierInfo *AttrName,
return false;
}

if (ScopeName && ScopeName->getName() == "gnu")
if (ScopeName && ScopeName->getName() == "gnu") {
// GNU-scoped attributes have some special cases to handle GNU-specific
// behaviors.
ParseGNUAttributeArgs(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName,
ScopeLoc, AttributeList::AS_CXX11, nullptr);
else {
unsigned NumArgs =
return true;
}

unsigned NumArgs;
// Some Clang-scoped attributes have some special parsing behavior.
if (ScopeName && ScopeName->getName() == "clang")
NumArgs =
ParseClangAttributeArgs(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName,
ScopeLoc, AttributeList::AS_CXX11);
else
NumArgs =
ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, AttributeList::AS_CXX11);

const AttributeList *Attr = Attrs.getList();
if (Attr && IsBuiltInOrStandardCXX11Attribute(AttrName, ScopeName)) {
// If the attribute is a standard or built-in attribute and we are
// parsing an argument list, we need to determine whether this attribute
// was allowed to have an argument list (such as [[deprecated]]), and how
// many arguments were parsed (so we can diagnose on [[deprecated()]]).
if (Attr->getMaxArgs() && !NumArgs) {
// The attribute was allowed to have arguments, but none were provided
// even though the attribute parsed successfully. This is an error.
Diag(LParenLoc, diag::err_attribute_requires_arguments) << AttrName;
Attr->setInvalid(true);
} else if (!Attr->getMaxArgs()) {
// The attribute parsed successfully, but was not allowed to have any
// arguments. It doesn't matter whether any were provided -- the
// presence of the argument list (even if empty) is diagnosed.
Diag(LParenLoc, diag::err_cxx11_attribute_forbids_arguments)
<< AttrName
<< FixItHint::CreateRemoval(SourceRange(LParenLoc, *EndLoc));
Attr->setInvalid(true);
}

const AttributeList *Attr = Attrs.getList();
if (Attr && IsBuiltInOrStandardCXX11Attribute(AttrName, ScopeName)) {
// If the attribute is a standard or built-in attribute and we are
// parsing an argument list, we need to determine whether this attribute
// was allowed to have an argument list (such as [[deprecated]]), and how
// many arguments were parsed (so we can diagnose on [[deprecated()]]).
if (Attr->getMaxArgs() && !NumArgs) {
// The attribute was allowed to have arguments, but none were provided
// even though the attribute parsed successfully. This is an error.
Diag(LParenLoc, diag::err_attribute_requires_arguments) << AttrName;
Attr->setInvalid(true);
} else if (!Attr->getMaxArgs()) {
// The attribute parsed successfully, but was not allowed to have any
// arguments. It doesn't matter whether any were provided -- the
// presence of the argument list (even if empty) is diagnosed.
Diag(LParenLoc, diag::err_cxx11_attribute_forbids_arguments)
<< AttrName
<< FixItHint::CreateRemoval(SourceRange(LParenLoc, *EndLoc));
Attr->setInvalid(true);
}
}
return true;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Parse/Parser.cpp
Expand Up @@ -493,6 +493,8 @@ void Parser::Initialize() {
Ident_strict = nullptr;
Ident_replacement = nullptr;

Ident_language = Ident_defined_in = Ident_generated_declaration = nullptr;

Ident__except = nullptr;

Ident__exception_code = Ident__exception_info = nullptr;
Expand Down

0 comments on commit d5d27e1

Please sign in to comment.