Skip to content

Commit

Permalink
[clang] Implement the using_if_exists attribute
Browse files Browse the repository at this point in the history
This attribute applies to a using declaration, and permits importing a
declaration without knowing if that declaration exists. This is useful
for libc++ C wrapper headers that re-export declarations in std::, in
cases where the base C library doesn't provide all declarations.

This attribute was proposed in http://lists.llvm.org/pipermail/cfe-dev/2020-June/066038.html.

rdar://69313357

Differential Revision: https://reviews.llvm.org/D90188
  • Loading branch information
epilk authored and ldionne committed Jun 2, 2021
1 parent 2f951ca commit 369c648
Show file tree
Hide file tree
Showing 24 changed files with 481 additions and 25 deletions.
22 changes: 22 additions & 0 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -3776,6 +3776,28 @@ class UnresolvedUsingTypenameDecl
static bool classofKind(Kind K) { return K == UnresolvedUsingTypename; }
};

/// This node is generated when a using-declaration that was annotated with
/// __attribute__((using_if_exists)) failed to resolve to a known declaration.
/// In that case, Sema builds a UsingShadowDecl whose target is an instance of
/// this declaration, adding it to the current scope. Referring to this
/// declaration in any way is an error.
class UnresolvedUsingIfExistsDecl final : public NamedDecl {
UnresolvedUsingIfExistsDecl(DeclContext *DC, SourceLocation Loc,
DeclarationName Name);

void anchor() override;

public:
static UnresolvedUsingIfExistsDecl *Create(ASTContext &Ctx, DeclContext *DC,
SourceLocation Loc,
DeclarationName Name);
static UnresolvedUsingIfExistsDecl *CreateDeserialized(ASTContext &Ctx,
unsigned ID);

static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Decl::UnresolvedUsingIfExists; }
};

/// Represents a C++11 static_assert declaration.
class StaticAssertDecl : public Decl {
llvm::PointerIntPair<Expr *, 1, bool> AssertExprAndFailed;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,8 @@ DEF_TRAVERSE_DECL(UnresolvedUsingTypenameDecl, {
// source.
})

DEF_TRAVERSE_DECL(UnresolvedUsingIfExistsDecl, {})

DEF_TRAVERSE_DECL(EnumDecl, {
TRY_TO(TraverseDeclTemplateParameterLists(D));

Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3750,6 +3750,14 @@ def NoBuiltin : Attr {
let Documentation = [NoBuiltinDocs];
}

def UsingIfExists : InheritableAttr {
let Spellings = [Clang<"using_if_exists", 0>];
let Subjects = SubjectList<[Using,
UnresolvedUsingTypename,
UnresolvedUsingValue], ErrorDiag>;
let Documentation = [UsingIfExistsDocs];
}

// FIXME: This attribute is not inheritable, it will not be propagated to
// redecls. [[clang::lifetimebound]] has the same problems. This should be
// fixed in TableGen (by probably adding a new inheritable flag).
Expand Down
25 changes: 25 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -5775,6 +5775,31 @@ once.
}];
}

def UsingIfExistsDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
The ``using_if_exists`` attribute applies to a using-declaration. It allows
programmers to import a declaration that potentially does not exist, instead
deferring any errors to the point of use. For instance:

.. code-block:: c++

namespace empty_namespace {};
__attribute__((using_if_exists))
using empty_namespace::does_not_exist; // no error!

does_not_exist x; // error: use of unresolved 'using_if_exists'

The C++ spelling of the attribte (`[[clang::using_if_exists]]`) is also
supported as a clang extension, since ISO C++ doesn't support attributes in this
position. If the entity referred to by the using-declaration is found by name
lookup, the attribute has no effect. This attribute is useful for libraries
(primarily, libc++) that wish to redeclare a set of declarations in another
namespace, when the availability of those declarations is difficult or
impossible to detect at compile time with the preprocessor.
}];
}

def HandleDocs : DocumentationCategory<"Handle Attributes"> {
let Content = [{
Handles are a way to identify resources like files, sockets, and processes.
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DeclNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def Named : DeclNode<Decl, "named declarations", 1>;
def UsingPack : DeclNode<Named>;
def UsingShadow : DeclNode<Named>;
def ConstructorUsingShadow : DeclNode<UsingShadow>;
def UnresolvedUsingIfExists : DeclNode<Named>;
def ObjCMethod : DeclNode<Named, "Objective-C methods">, DeclContext;
def ObjCContainer : DeclNode<Named, "Objective-C containers", 1>, DeclContext;
def ObjCCategory : DeclNode<ObjCContainer>;
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,12 @@ def err_using_decl_conflict_reverse : Error<
def note_using_decl : Note<"%select{|previous }0using declaration">;
def err_using_decl_redeclaration_expansion : Error<
"using declaration pack expansion at block scope produces multiple values">;
def err_use_of_empty_using_if_exists : Error<
"reference to unresolved using declaration">;
def note_empty_using_if_exists_here : Note<
"using declaration annotated with 'using_if_exists' here">;
def err_using_if_exists_on_ctor : Error<
"'using_if_exists' attribute cannot be applied to an inheriting constructor">;

def warn_access_decl_deprecated : Warning<
"access declarations are deprecated; use using declarations instead">,
Expand Down
13 changes: 8 additions & 5 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -5720,11 +5720,14 @@ class Sema final {
const DeclarationNameInfo &NameInfo,
SourceLocation NameLoc);

NamedDecl *BuildUsingDeclaration(
Scope *S, AccessSpecifier AS, SourceLocation UsingLoc,
bool HasTypenameKeyword, SourceLocation TypenameLoc, CXXScopeSpec &SS,
DeclarationNameInfo NameInfo, SourceLocation EllipsisLoc,
const ParsedAttributesView &AttrList, bool IsInstantiation);
NamedDecl *BuildUsingDeclaration(Scope *S, AccessSpecifier AS,
SourceLocation UsingLoc,
bool HasTypenameKeyword,
SourceLocation TypenameLoc, CXXScopeSpec &SS,
DeclarationNameInfo NameInfo,
SourceLocation EllipsisLoc,
const ParsedAttributesView &AttrList,
bool IsInstantiation, bool IsUsingIfExists);
NamedDecl *BuildUsingPackDecl(NamedDecl *InstantiatedFrom,
ArrayRef<NamedDecl *> Expansions);

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,9 @@ enum DeclCode {
/// \brief A ConceptDecl record.
DECL_CONCEPT,

/// An UnresolvedUsingIfExistsDecl record.
DECL_UNRESOLVED_USING_IF_EXISTS,

/// \brief A StaticAssertDecl record.
DECL_STATIC_ASSERT,

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/DeclBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,9 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
case TypeAliasTemplate:
return IDNS_Ordinary | IDNS_Tag | IDNS_Type;

case UnresolvedUsingIfExists:
return IDNS_Type | IDNS_Ordinary;

case OMPDeclareReduction:
return IDNS_OMPReduction;

Expand Down
19 changes: 19 additions & 0 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3150,6 +3150,25 @@ UnresolvedUsingTypenameDecl::CreateDeserialized(ASTContext &C, unsigned ID) {
SourceLocation(), nullptr, SourceLocation());
}

UnresolvedUsingIfExistsDecl *
UnresolvedUsingIfExistsDecl::Create(ASTContext &Ctx, DeclContext *DC,
SourceLocation Loc, DeclarationName Name) {
return new (Ctx, DC) UnresolvedUsingIfExistsDecl(DC, Loc, Name);
}

UnresolvedUsingIfExistsDecl *
UnresolvedUsingIfExistsDecl::CreateDeserialized(ASTContext &Ctx, unsigned ID) {
return new (Ctx, ID)
UnresolvedUsingIfExistsDecl(nullptr, SourceLocation(), DeclarationName());
}

UnresolvedUsingIfExistsDecl::UnresolvedUsingIfExistsDecl(DeclContext *DC,
SourceLocation Loc,
DeclarationName Name)
: NamedDecl(Decl::UnresolvedUsingIfExists, DC, Loc, Name) {}

void UnresolvedUsingIfExistsDecl::anchor() {}

void StaticAssertDecl::anchor() {}

StaticAssertDecl *StaticAssertDecl::Create(ASTContext &C, DeclContext *DC,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/CGDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ void CodeGenFunction::EmitDecl(const Decl &D) {
case Decl::ConstructorUsingShadow:
case Decl::ObjCTypeParam:
case Decl::Binding:
case Decl::UnresolvedUsingIfExists:
llvm_unreachable("Declaration should not be in declstmts!");
case Decl::Record: // struct/union/class X;
case Decl::CXXRecord: // struct/union/class X; [C++]
Expand Down
23 changes: 18 additions & 5 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,14 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
// Look to see if we have a type anywhere in the list of results.
for (LookupResult::iterator Res = Result.begin(), ResEnd = Result.end();
Res != ResEnd; ++Res) {
if (isa<TypeDecl>(*Res) || isa<ObjCInterfaceDecl>(*Res) ||
(AllowDeducedTemplate && getAsTypeTemplateDecl(*Res))) {
if (!IIDecl || (*Res)->getLocation() < IIDecl->getLocation())
IIDecl = *Res;
NamedDecl *RealRes = (*Res)->getUnderlyingDecl();
if (isa<TypeDecl, ObjCInterfaceDecl, UnresolvedUsingIfExistsDecl>(
RealRes) ||
(AllowDeducedTemplate && getAsTypeTemplateDecl(RealRes))) {
if (!IIDecl ||
// Make the selection of the recovery decl deterministic.
RealRes->getLocation() < IIDecl->getLocation())
IIDecl = RealRes;
}
}

Expand Down Expand Up @@ -486,6 +490,10 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
(void)DiagnoseUseOfDecl(IDecl, NameLoc);
if (!HasTrailingDot)
T = Context.getObjCInterfaceType(IDecl);
} else if (auto *UD = dyn_cast<UnresolvedUsingIfExistsDecl>(IIDecl)) {
(void)DiagnoseUseOfDecl(UD, NameLoc);
// Recover with 'int'
T = Context.IntTy;
} else if (AllowDeducedTemplate) {
if (auto *TD = getAsTypeTemplateDecl(IIDecl))
T = Context.getDeducedTemplateSpecializationType(TemplateName(TD),
Expand All @@ -502,7 +510,7 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
// constructor or destructor name (in such a case, the scope specifier
// will be attached to the enclosing Expr or Decl node).
if (SS && SS->isNotEmpty() && !IsCtorOrDtorName &&
!isa<ObjCInterfaceDecl>(IIDecl)) {
!isa<ObjCInterfaceDecl, UnresolvedUsingIfExistsDecl>(IIDecl)) {
if (WantNontrivialTypeSourceInfo) {
// Construct a type with type-source information.
TypeLocBuilder Builder;
Expand Down Expand Up @@ -1161,6 +1169,11 @@ Sema::NameClassification Sema::ClassifyName(Scope *S, CXXScopeSpec &SS,
return NameClassification::Concept(
TemplateName(cast<TemplateDecl>(FirstDecl)));

if (auto *EmptyD = dyn_cast<UnresolvedUsingIfExistsDecl>(FirstDecl)) {
(void)DiagnoseUseOfDecl(EmptyD, NameLoc);
return NameClassification::Error();
}

// We can have a type template here if we're classifying a template argument.
if (isa<TemplateDecl>(FirstDecl) && !isa<FunctionTemplateDecl>(FirstDecl) &&
!isa<VarTemplateDecl>(FirstDecl))
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8332,6 +8332,10 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_BuiltinAlias:
handleBuiltinAliasAttr(S, D, AL);
break;

case ParsedAttr::AT_UsingIfExists:
handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
break;
}
}

Expand Down
47 changes: 41 additions & 6 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11604,10 +11604,11 @@ Decl *Sema::ActOnUsingDeclaration(Scope *S, AccessSpecifier AS,
}
}

NamedDecl *UD =
BuildUsingDeclaration(S, AS, UsingLoc, TypenameLoc.isValid(), TypenameLoc,
SS, TargetNameInfo, EllipsisLoc, AttrList,
/*IsInstantiation*/false);
NamedDecl *UD = BuildUsingDeclaration(
S, AS, UsingLoc, TypenameLoc.isValid(), TypenameLoc, SS, TargetNameInfo,
EllipsisLoc, AttrList,
/*IsInstantiation=*/false,
AttrList.hasAttribute(ParsedAttr::AT_UsingIfExists));
if (UD)
PushOnScopeChains(UD, S, /*AddToContext*/ false);

Expand All @@ -11627,6 +11628,12 @@ IsEquivalentForUsingDecl(ASTContext &Context, NamedDecl *D1, NamedDecl *D2) {
return Context.hasSameType(TD1->getUnderlyingType(),
TD2->getUnderlyingType());

// Two using_if_exists using-declarations are equivalent if both are
// unresolved.
if (isa<UnresolvedUsingIfExistsDecl>(D1) &&
isa<UnresolvedUsingIfExistsDecl>(D2))
return true;

return false;
}

Expand Down Expand Up @@ -11737,6 +11744,20 @@ bool Sema::CheckUsingShadowDecl(UsingDecl *Using, NamedDecl *Orig,
if (FoundEquivalentDecl)
return false;

// Always emit a diagnostic for a mismatch between an unresolved
// using_if_exists and a resolved using declaration in either direction.
if (isa<UnresolvedUsingIfExistsDecl>(Target) !=
(isa_and_nonnull<UnresolvedUsingIfExistsDecl>(NonTag))) {
if (!NonTag && !Tag)
return false;
Diag(Using->getLocation(), diag::err_using_decl_conflict);
Diag(Target->getLocation(), diag::note_using_decl_target);
Diag((NonTag ? NonTag : Tag)->getLocation(),
diag::note_using_decl_conflict);
Using->setInvalidDecl();
return true;
}

if (FunctionDecl *FD = Target->getAsFunction()) {
NamedDecl *OldDecl = nullptr;
switch (CheckOverload(nullptr, FD, Previous, OldDecl,
Expand Down Expand Up @@ -12001,7 +12022,8 @@ NamedDecl *Sema::BuildUsingDeclaration(
Scope *S, AccessSpecifier AS, SourceLocation UsingLoc,
bool HasTypenameKeyword, SourceLocation TypenameLoc, CXXScopeSpec &SS,
DeclarationNameInfo NameInfo, SourceLocation EllipsisLoc,
const ParsedAttributesView &AttrList, bool IsInstantiation) {
const ParsedAttributesView &AttrList, bool IsInstantiation,
bool IsUsingIfExists) {
assert(!SS.isInvalid() && "Invalid CXXScopeSpec.");
SourceLocation IdentLoc = NameInfo.getLoc();
assert(IdentLoc.isValid() && "Invalid TargetName location.");
Expand Down Expand Up @@ -12070,6 +12092,13 @@ NamedDecl *Sema::BuildUsingDeclaration(
IdentLoc))
return nullptr;

// 'using_if_exists' doesn't make sense on an inherited constructor.
if (IsUsingIfExists && UsingName.getName().getNameKind() ==
DeclarationName::CXXConstructorName) {
Diag(UsingLoc, diag::err_using_if_exists_on_ctor);
return nullptr;
}

DeclContext *LookupContext = computeDeclContext(SS);
NestedNameSpecifierLoc QualifierLoc = SS.getWithLocInContext(Context);
if (!LookupContext || EllipsisLoc.isValid()) {
Expand Down Expand Up @@ -12126,6 +12155,11 @@ NamedDecl *Sema::BuildUsingDeclaration(

LookupQualifiedName(R, LookupContext);

if (R.empty() && IsUsingIfExists)
R.addDecl(UnresolvedUsingIfExistsDecl::Create(Context, CurContext, UsingLoc,
UsingName.getName()),
AS_public);

// Try to correct typos if possible. If constructor name lookup finds no
// results, that means the named class has no explicit constructors, and we
// suppressed declaring implicit ones (probably because it's dependent or
Expand Down Expand Up @@ -12199,7 +12233,8 @@ NamedDecl *Sema::BuildUsingDeclaration(

if (HasTypenameKeyword) {
// If we asked for a typename and got a non-type decl, error out.
if (!R.getAsSingle<TypeDecl>()) {
if (!R.getAsSingle<TypeDecl>() &&
!R.getAsSingle<UnresolvedUsingIfExistsDecl>()) {
Diag(IdentLoc, diag::err_using_typename_non_type);
for (LookupResult::iterator I = R.begin(), E = R.end(); I != E; ++I)
Diag((*I)->getUnderlyingDecl()->getLocation(),
Expand Down
16 changes: 13 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ bool Sema::CanUseDecl(NamedDecl *D, bool TreatUnavailableAsInvalid) {
cast<Decl>(CurContext)->getAvailability() != AR_Unavailable)
return false;

if (isa<UnresolvedUsingIfExistsDecl>(D))
return false;

return true;
}

Expand Down Expand Up @@ -348,6 +351,12 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
return true;
}

if (const auto *EmptyD = dyn_cast<UnresolvedUsingIfExistsDecl>(D)) {
Diag(Loc, diag::err_use_of_empty_using_if_exists);
Diag(EmptyD->getLocation(), diag::note_empty_using_if_exists_here);
return true;
}

DiagnoseAvailabilityOfDecl(D, Locs, UnknownObjCClass, ObjCPropertyAccess,
AvoidPartialAvailabilityChecks, ClassReceiver);

Expand Down Expand Up @@ -3208,8 +3217,7 @@ ExprResult Sema::BuildDeclarationNameExpr(
}

// Make sure that we're referring to a value.
ValueDecl *VD = dyn_cast<ValueDecl>(D);
if (!VD) {
if (!isa<ValueDecl, UnresolvedUsingIfExistsDecl>(D)) {
Diag(Loc, diag::err_ref_non_value)
<< D << SS.getRange();
Diag(D->getLocation(), diag::note_declared_at);
Expand All @@ -3220,9 +3228,11 @@ ExprResult Sema::BuildDeclarationNameExpr(
// this check when we're going to perform argument-dependent lookup
// on this function name, because this might not be the function
// that overload resolution actually selects.
if (DiagnoseUseOfDecl(VD, Loc))
if (DiagnoseUseOfDecl(D, Loc))
return ExprError();

auto *VD = cast<ValueDecl>(D);

// Only create DeclRefExpr's for valid Decl's.
if (VD->isInvalidDecl() && !AcceptInvalidDecl)
return ExprError();
Expand Down
Loading

0 comments on commit 369c648

Please sign in to comment.