Qualified lookup through using declarations. Diagnose a new type of ambiguity.

Split the various ambiguous result enumerators into their own enum.  Tests
for most of C++ [namespace.qual].

llvm-svn: 83700
This commit is contained in:
John McCall 2009-10-10 05:48:19 +00:00
parent b8120770b4
commit 6538c93050
10 changed files with 465 additions and 50 deletions

View File

@ -1996,6 +1996,10 @@ def err_ambiguous_member_multiple_subobject_types : Error<
def note_ambiguous_member_found : Note<"member found by ambiguous name lookup">;
def err_ambiguous_reference : Error<"reference to %0 is ambiguous">;
def note_ambiguous_candidate : Note<"candidate found by name lookup is %q0">;
def err_ambiguous_tag_hiding : Error<"a type named %0 is hidden by a "
"declaration in a different namespace">;
def note_hidden_tag : Note<"type declaration hidden">;
def note_hiding_object : Note<"declaration hides type">;
// C++ operator overloading
def err_operator_overload_needs_class_or_enum : Error<

View File

@ -1074,6 +1074,13 @@ public:
/// functions into an OverloadedFunctionDecl.
FoundOverloaded,
/// @brief Name lookup results in an ambiguity; use
/// getAmbiguityKind to figure out what kind of ambiguity
/// we have.
Ambiguous
};
enum AmbiguityKind {
/// Name lookup results in an ambiguity because multiple
/// entities that meet the lookup criteria were found in
/// subobjects of different types. For example:
@ -1117,7 +1124,22 @@ public:
/// @endcode
AmbiguousReference,
FirstAmbiguous = AmbiguousBaseSubobjectTypes
/// Name lookup results in an ambiguity because an entity with a
/// tag name was hidden by an entity with an ordinary name from
/// a different context.
/// @code
/// namespace A { struct Foo {}; }
/// namespace B { void Foo(); }
/// namespace C {
/// using namespace A;
/// using namespace B;
/// }
/// void test() {
/// C::Foo(); // error: tag 'A::Foo' is hidden by an object in a
/// // different namespace
/// }
/// @endcode
AmbiguousTagHiding
};
typedef llvm::SmallVector<NamedDecl*, 4> DeclsTy;
@ -1132,7 +1154,7 @@ public:
}
bool isAmbiguous() const {
return getKind() >= FirstAmbiguous;
return getKind() == Ambiguous;
}
LookupKind getKind() const {
@ -1140,6 +1162,11 @@ public:
return Kind;
}
AmbiguityKind getAmbiguityKind() const {
assert(isAmbiguous());
return Ambiguity;
}
iterator begin() const { return Decls.begin(); }
iterator end() const { return Decls.end(); }
@ -1158,6 +1185,25 @@ public:
Kind = Found;
}
/// \brief Add all the declarations from another set of lookup
/// results.
void addAllDecls(const LookupResult &Other) {
Decls.append(Other.begin(), Other.end());
Kind = Found;
}
/// \brief Hides a set of declarations.
template <class NamedDeclSet> void hideDecls(const NamedDeclSet &Set) {
unsigned I = 0, N = Decls.size();
while (I < N) {
if (Set.count(Decls[I]))
Decls[I] = Decls[--N];
else
I++;
}
Decls.set_size(N);
}
/// \brief Resolves the kind of the lookup, possibly hiding decls.
///
/// This should be called in any environment where lookup might
@ -1181,6 +1227,11 @@ public:
return *Decls.begin();
}
/// \brief Asks if the result is a single tag decl.
bool isSingleTagDecl() const {
return getKind() == Found && isa<TagDecl>(getFoundDecl());
}
/// \brief Make these results show that the name was found in
/// base classes of different types.
///
@ -1193,6 +1244,13 @@ public:
/// The given paths object is copied and invalidated.
void setAmbiguousBaseSubobjects(CXXBasePaths &P);
/// \brief Make these results show that the name was found in
/// different contexts and a tag decl was hidden by an ordinary
/// decl in a different context.
void setAmbiguousQualifiedTagHiding() {
setAmbiguous(AmbiguousTagHiding);
}
/// \brief Clears out any current state.
void clear() {
Kind = NotFound;
@ -1204,6 +1262,11 @@ public:
void print(llvm::raw_ostream &);
private:
void setAmbiguous(AmbiguityKind AK) {
Kind = Ambiguous;
Ambiguity = AK;
}
void addDeclsFromBasePaths(const CXXBasePaths &P);
// Sanity checks.
@ -1211,14 +1274,17 @@ public:
assert(Kind != NotFound || Decls.size() == 0);
assert(Kind != Found || Decls.size() == 1);
assert(Kind == NotFound || Kind == Found ||
Kind == AmbiguousBaseSubobjects || Decls.size() > 1);
assert((Paths != NULL) == (Kind == AmbiguousBaseSubobjectTypes ||
Kind == AmbiguousBaseSubobjects));
(Kind == Ambiguous && Ambiguity == AmbiguousBaseSubobjects)
|| Decls.size() > 1);
assert((Paths != NULL) == (Kind == Ambiguous &&
(Ambiguity == AmbiguousBaseSubobjectTypes ||
Ambiguity == AmbiguousBaseSubobjects)));
}
static void deletePaths(CXXBasePaths *);
LookupKind Kind;
AmbiguityKind Ambiguity; // ill-defined unless ambiguous
DeclsTy Decls;
CXXBasePaths *Paths;
};

View File

@ -94,9 +94,15 @@ Sema::TypeTy *Sema::getTypeName(IdentifierInfo &II, SourceLocation NameLoc,
case LookupResult::FoundOverloaded:
return 0;
case LookupResult::AmbiguousBaseSubobjectTypes:
case LookupResult::AmbiguousBaseSubobjects:
case LookupResult::AmbiguousReference: {
case LookupResult::Ambiguous: {
// Recover from type-hiding ambiguities by hiding the type. We'll
// do the lookup again when looking for an object, and we can
// diagnose the error then. If we don't do this, then the error
// about hiding the type will be immediately followed by an error
// that only makes sense if the identifier was treated like a type.
if (Result.getAmbiguityKind() == LookupResult::AmbiguousTagHiding)
return 0;
// 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) {
@ -4129,6 +4135,8 @@ Sema::DeclPtrTy Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
bool isStdBadAlloc = false;
bool Invalid = false;
bool RedeclarationOnly = (TUK != TUK_Reference);
if (Name && SS.isNotEmpty()) {
// We have a nested-name tag ('struct foo::bar').
@ -4155,11 +4163,18 @@ Sema::DeclPtrTy Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
SearchDC = DC;
// Look-up name inside 'foo::'.
LookupResult R;
LookupQualifiedName(R, DC, Name, LookupTagName, true);
PrevDecl = dyn_cast_or_null<TagDecl>(R.getAsSingleDecl(Context));
LookupQualifiedName(R, DC, Name, LookupTagName, RedeclarationOnly);
if (R.isAmbiguous()) {
DiagnoseAmbiguousLookup(R, Name, NameLoc, SS.getRange());
return DeclPtrTy();
}
if (R.getKind() == LookupResult::Found)
PrevDecl = dyn_cast<TagDecl>(R.getFoundDecl());
// A tag 'foo::bar' must already exist.
if (PrevDecl == 0) {
if (!PrevDecl) {
Diag(NameLoc, diag::err_not_tag_in_scope) << Name << SS.getRange();
Name = 0;
Invalid = true;
@ -4172,8 +4187,7 @@ Sema::DeclPtrTy Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
// shouldn't be. Doing so can result in ambiguities that we
// shouldn't be diagnosing.
LookupResult R;
LookupName(R, S, Name, LookupTagName,
/*RedeclarationOnly=*/(TUK != TUK_Reference));
LookupName(R, S, Name, LookupTagName, RedeclarationOnly);
if (R.isAmbiguous()) {
DiagnoseAmbiguousLookup(R, Name, NameLoc);
// FIXME: This is not best way to recover from case like:

View File

@ -25,6 +25,7 @@
#include "clang/Basic/LangOptions.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/ErrorHandling.h"
#include <set>
#include <vector>
#include <iterator>
@ -162,6 +163,9 @@ void Sema::LookupResult::resolveKind() {
// Fast case: no possible ambiguity.
if (N <= 1) return;
// Don't do any extra resolution if we've already resolved as ambiguous.
if (Kind == Ambiguous) return;
llvm::SmallPtrSet<NamedDecl*, 16> Unique;
bool Ambiguous = false;
@ -218,11 +222,11 @@ void Sema::LookupResult::resolveKind() {
Ambiguous = true;
if (Ambiguous)
Kind = AmbiguousReference;
setAmbiguous(LookupResult::AmbiguousReference);
else if (N > 1)
Kind = FoundOverloaded;
Kind = LookupResult::FoundOverloaded;
else
Kind = Found;
Kind = LookupResult::Found;
}
/// @brief Converts the result of name lookup into a single (possible
@ -278,7 +282,7 @@ void Sema::LookupResult::setAmbiguousBaseSubobjects(CXXBasePaths &P) {
Paths->swap(P);
addDeclsFromBasePaths(*Paths);
resolveKind();
Kind = AmbiguousBaseSubobjects;
setAmbiguous(AmbiguousBaseSubobjects);
}
void Sema::LookupResult::setAmbiguousBaseSubobjectTypes(CXXBasePaths &P) {
@ -286,7 +290,7 @@ void Sema::LookupResult::setAmbiguousBaseSubobjectTypes(CXXBasePaths &P) {
Paths->swap(P);
addDeclsFromBasePaths(*Paths);
resolveKind();
Kind = AmbiguousBaseSubobjectTypes;
setAmbiguous(AmbiguousBaseSubobjectTypes);
}
void Sema::LookupResult::print(llvm::raw_ostream &Out) {
@ -644,6 +648,120 @@ bool Sema::LookupName(LookupResult &R, Scope *S, DeclarationName Name,
return false;
}
/// @brief Perform qualified name lookup in the namespaces nominated by
/// using directives by the given context.
///
/// C++98 [namespace.qual]p2:
/// Given X::m (where X is a user-declared namespace), or given ::m
/// (where X is the global namespace), let S be the set of all
/// declarations of m in X and in the transitive closure of all
/// namespaces nominated by using-directives in X and its used
/// namespaces, except that using-directives are ignored in any
/// namespace, including X, directly containing one or more
/// declarations of m. No namespace is searched more than once in
/// the lookup of a name. If S is the empty set, the program is
/// ill-formed. Otherwise, if S has exactly one member, or if the
/// context of the reference is a using-declaration
/// (namespace.udecl), S is the required set of declarations of
/// m. Otherwise if the use of m is not one that allows a unique
/// declaration to be chosen from S, the program is ill-formed.
/// C++98 [namespace.qual]p5:
/// During the lookup of a qualified namespace member name, if the
/// lookup finds more than one declaration of the member, and if one
/// declaration introduces a class name or enumeration name and the
/// other declarations either introduce the same object, the same
/// enumerator or a set of functions, the non-type name hides the
/// class or enumeration name if and only if the declarations are
/// from the same namespace; otherwise (the declarations are from
/// different namespaces), the program is ill-formed.
static bool LookupQualifiedNameInUsingDirectives(Sema::LookupResult &R,
DeclContext *StartDC,
DeclarationName Name,
Sema::LookupNameKind NameKind,
unsigned IDNS) {
assert(StartDC->isFileContext() && "start context is not a file context");
DeclContext::udir_iterator I = StartDC->using_directives_begin();
DeclContext::udir_iterator E = StartDC->using_directives_end();
if (I == E) return false;
// We have at least added all these contexts to the queue.
llvm::DenseSet<DeclContext*> Visited;
Visited.insert(StartDC);
// We have not yet looked into these namespaces, much less added
// their "using-children" to the queue.
llvm::SmallVector<NamespaceDecl*, 8> Queue;
// We have already looked into the initial namespace; seed the queue
// with its using-children.
for (; I != E; ++I) {
NamespaceDecl *ND = (*I)->getNominatedNamespace();
if (Visited.insert(ND).second)
Queue.push_back(ND);
}
// The easiest way to implement the restriction in [namespace.qual]p5
// is to check whether any of the individual results found a tag
// and, if so, to declare an ambiguity if the final result is not
// a tag.
bool FoundTag = false;
bool FoundNonTag = false;
Sema::LookupResult LocalR;
bool Found = false;
while (!Queue.empty()) {
NamespaceDecl *ND = Queue.back();
Queue.pop_back();
// We go through some convolutions here to avoid copying results
// between LookupResults.
bool UseLocal = !R.empty();
Sema::LookupResult &DirectR = UseLocal ? LocalR : R;
bool FoundDirect = LookupDirect(DirectR, ND, Name, NameKind, IDNS);
if (FoundDirect) {
// First do any local hiding.
DirectR.resolveKind();
// If the local result is a tag, remember that.
if (DirectR.isSingleTagDecl())
FoundTag = true;
else
FoundNonTag = true;
// Append the local results to the total results if necessary.
if (UseLocal) {
R.addAllDecls(LocalR);
LocalR.clear();
}
}
// If we find names in this namespace, ignore its using directives.
if (FoundDirect) {
Found = true;
continue;
}
for (llvm::tie(I,E) = ND->getUsingDirectives(); I != E; ++I) {
NamespaceDecl *Nom = (*I)->getNominatedNamespace();
if (Visited.insert(Nom).second)
Queue.push_back(Nom);
}
}
if (Found) {
if (FoundTag && FoundNonTag)
R.setAmbiguousQualifiedTagHiding();
else
R.resolveKind();
}
return Found;
}
/// @brief Perform qualified name lookup into a given context.
///
/// Qualified name lookup (C++ [basic.lookup.qual]) is used to find
@ -704,9 +822,26 @@ bool Sema::LookupQualifiedName(LookupResult &R, DeclContext *LookupCtx,
return true;
}
// Don't descend into implied contexts for redeclarations.
// C++98 [namespace.qual]p6:
// In a declaration for a namespace member in which the
// declarator-id is a qualified-id, given that the qualified-id
// for the namespace member has the form
// nested-name-specifier unqualified-id
// the unqualified-id shall name a member of the namespace
// designated by the nested-name-specifier.
// See also [class.mfct]p5 and [class.static.data]p2.
if (RedeclarationOnly)
return false;
// If this is a namespace, look it up in
if (LookupCtx->isFileContext())
return LookupQualifiedNameInUsingDirectives(R, LookupCtx, Name, NameKind,
IDNS);
// If this isn't a C++ class, we aren't allowed to look into base
// classes, we're done.
if (RedeclarationOnly || !isa<CXXRecordDecl>(LookupCtx))
if (!isa<CXXRecordDecl>(LookupCtx))
return false;
// Perform lookup into our base classes.
@ -895,31 +1030,32 @@ bool Sema::DiagnoseAmbiguousLookup(LookupResult &Result, DeclarationName Name,
SourceRange LookupRange) {
assert(Result.isAmbiguous() && "Lookup result must be ambiguous");
if (CXXBasePaths *Paths = Result.getBasePaths()) {
if (Result.getKind() == LookupResult::AmbiguousBaseSubobjects) {
QualType SubobjectType = Paths->front().back().Base->getType();
Diag(NameLoc, diag::err_ambiguous_member_multiple_subobjects)
<< Name << SubobjectType << getAmbiguousPathsDisplayString(*Paths)
<< LookupRange;
switch (Result.getAmbiguityKind()) {
case LookupResult::AmbiguousBaseSubobjects: {
CXXBasePaths *Paths = Result.getBasePaths();
QualType SubobjectType = Paths->front().back().Base->getType();
Diag(NameLoc, diag::err_ambiguous_member_multiple_subobjects)
<< Name << SubobjectType << getAmbiguousPathsDisplayString(*Paths)
<< LookupRange;
DeclContext::lookup_iterator Found = Paths->front().Decls.first;
while (isa<CXXMethodDecl>(*Found) &&
cast<CXXMethodDecl>(*Found)->isStatic())
++Found;
DeclContext::lookup_iterator Found = Paths->front().Decls.first;
while (isa<CXXMethodDecl>(*Found) &&
cast<CXXMethodDecl>(*Found)->isStatic())
++Found;
Diag((*Found)->getLocation(), diag::note_ambiguous_member_found);
Diag((*Found)->getLocation(), diag::note_ambiguous_member_found);
return true;
}
assert(Result.getKind() == LookupResult::AmbiguousBaseSubobjectTypes &&
"Unhandled form of name lookup ambiguity");
return true;
}
case LookupResult::AmbiguousBaseSubobjectTypes: {
Diag(NameLoc, diag::err_ambiguous_member_multiple_subobject_types)
<< Name << LookupRange;
CXXBasePaths *Paths = Result.getBasePaths();
std::set<Decl *> DeclsPrinted;
for (CXXBasePaths::paths_iterator Path = Paths->begin(), PathEnd = Paths->end();
for (CXXBasePaths::paths_iterator Path = Paths->begin(),
PathEnd = Paths->end();
Path != PathEnd; ++Path) {
Decl *D = *Path->Decls.first;
if (DeclsPrinted.insert(D).second)
@ -929,15 +1065,40 @@ bool Sema::DiagnoseAmbiguousLookup(LookupResult &Result, DeclarationName Name,
return true;
}
assert(Result.getKind() == LookupResult::AmbiguousReference &&
"unhandled form of name lookup ambiguity");
Diag(NameLoc, diag::err_ambiguous_reference) << Name << LookupRange;
case LookupResult::AmbiguousTagHiding: {
Diag(NameLoc, diag::err_ambiguous_tag_hiding) << Name << LookupRange;
llvm::SmallPtrSet<NamedDecl*,8> TagDecls;
LookupResult::iterator DI = Result.begin(), DE = Result.end();
for (; DI != DE; ++DI)
Diag((*DI)->getLocation(), diag::note_ambiguous_candidate) << *DI;
LookupResult::iterator DI, DE = Result.end();
for (DI = Result.begin(); DI != DE; ++DI)
if (TagDecl *TD = dyn_cast<TagDecl>(*DI)) {
TagDecls.insert(TD);
Diag(TD->getLocation(), diag::note_hidden_tag);
}
for (DI = Result.begin(); DI != DE; ++DI)
if (!isa<TagDecl>(*DI))
Diag((*DI)->getLocation(), diag::note_hiding_object);
// For recovery purposes, go ahead and implement the hiding.
Result.hideDecls(TagDecls);
return true;
}
case LookupResult::AmbiguousReference: {
Diag(NameLoc, diag::err_ambiguous_reference) << Name << LookupRange;
LookupResult::iterator DI = Result.begin(), DE = Result.end();
for (; DI != DE; ++DI)
Diag((*DI)->getLocation(), diag::note_ambiguous_candidate) << *DI;
return true;
}
}
llvm::llvm_unreachable("unknown ambiguity kind");
return true;
}

View File

@ -3858,9 +3858,7 @@ Sema::CheckTypenameType(NestedNameSpecifier *NNS, const IdentifierInfo &II,
Referenced = *Result.begin();
break;
case LookupResult::AmbiguousBaseSubobjectTypes:
case LookupResult::AmbiguousBaseSubobjects:
case LookupResult::AmbiguousReference:
case LookupResult::Ambiguous:
DiagnoseAmbiguousLookup(Result, Name, Range.getEnd(), Range);
return QualType();
}

View File

@ -194,7 +194,13 @@ QualType Sema::ConvertDeclSpecToType(const DeclSpec &DS,
case DeclSpec::TST_union:
case DeclSpec::TST_struct: {
Decl *D = static_cast<Decl *>(DS.getTypeRep());
assert(D && "Didn't get a decl for a class/enum/union/struct?");
if (!D) {
// This can happen in C++ with ambiguous lookups.
Result = Context.IntTy;
isInvalid = true;
break;
}
assert(DS.getTypeSpecWidth() == 0 && DS.getTypeSpecComplex() == 0 &&
DS.getTypeSpecSign() == 0 &&
"Can't handle qualifiers on typedef names yet!");

View File

@ -0,0 +1,65 @@
// RUN: clang-cc -fsyntax-only -verify %s
namespace Ints {
int zero = 0; // expected-note {{candidate found by name lookup is 'Ints::zero'}}
void f(int); // expected-note 3 {{candidate function}}
void g(int);
}
namespace Floats {
float zero = 0.0f; // expected-note {{candidate found by name lookup is 'Floats::zero'}}
void f(float); // expected-note 3 {{candidate function}}
void g(float);
}
namespace Numbers {
using namespace Ints;
using namespace Floats;
}
void test() {
int i = Ints::zero;
Ints::f(i);
float f = Floats::zero;
Floats::f(f);
double n = Numbers::zero; // expected-error {{reference to 'zero' is ambiguous}}
Numbers::f(n); // expected-error{{call to 'f' is ambiguous}}
Numbers::f(i);
Numbers::f(f);
}
namespace Numbers {
struct Number {
explicit Number(double d) : d(d) {}
double d;
};
Number zero(0.0f);
void g(Number);
}
void test2() {
Numbers::Number n = Numbers::zero;
Numbers::f(n); // expected-error {{no matching function for call to 'f'}}
Numbers::g(n);
}
namespace Numbers2 {
using Numbers::f;
using Numbers::g;
}
void test3() {
Numbers::Number n = Numbers::zero;
Numbers2::f(n); // expected-error {{no matching function for call to 'f'}}
Numbers2::g(n);
int i = Ints::zero;
Numbers2::f(i);
Numbers2::g(i); // expected-error {{incompatible type passing 'int'}}
float f = Floats::zero;
Numbers2::f(f);
Numbers2::g(f); // expected-error {{incompatible type passing 'float'}}
}

View File

@ -0,0 +1,41 @@
// RUN: clang-cc -fsyntax-only -verify %s
// This is basically paraphrased from the standard.
namespace Root {
int i = 0;
void f();
}
namespace A {
using namespace Root;
}
namespace B {
using namespace Root;
}
namespace AB {
using namespace A;
using namespace B;
}
void test() {
if (AB::i)
AB::f();
}
namespace C {
using Root::i;
using Root::f;
}
namespace AC {
using namespace A;
using namespace C;
}
void test2() {
if (AC::i)
AC::f();
}

View File

@ -0,0 +1,25 @@
// RUN: clang-cc -fsyntax-only -verify %s
namespace A {
int a;
}
namespace C {
int c;
}
namespace B {
using namespace C;
int b;
}
namespace C {
using namespace B;
using namespace A;
}
void test() {
C::a++;
C::b++;
C::c++;
}

View File

@ -0,0 +1,35 @@
// RUN: clang-cc -fsyntax-only -verify %s
namespace A {
struct x {}; // expected-note {{candidate found by name lookup is 'A::x'}}
int x; // expected-note {{candidate found by name lookup is 'A::x'}}
struct y {}; // expected-note {{type declaration hidden}}
struct z;
void z(float);
}
namespace B {
struct x {}; // expected-note {{candidate found by name lookup is 'B::x'}}
float x; // expected-note {{candidate found by name lookup is 'B::x'}}
float y; // expected-note {{declaration hides type}}
void z(int);
}
namespace AB {
using namespace A;
using namespace B;
}
void test() {
struct AB::x foo; // expected-error {{reference to 'x' is ambiguous}}
int i = AB::x; // expected-error {{reference to 'x' is ambiguous}}
struct AB::y bar;
float f = AB::y; // expected-error {{a type named 'y' is hidden by a declaration in a different namespace}}
AB::z(i);
AB::z(f);
}