diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -425,6 +425,7 @@ def CXX11LongLong : DiagGroup<"c++11-long-long">; def LongLong : DiagGroup<"long-long", [CXX11LongLong]>; def ImplicitlyUnsignedLiteral : DiagGroup<"implicitly-unsigned-literal">; +def MemberShadowsUse : DiagGroup<"member-shadows-use">; def MethodSignatures : DiagGroup<"method-signatures">; def MismatchedParameterTypes : DiagGroup<"mismatched-parameter-types">; def MismatchedReturnTypes : DiagGroup<"mismatched-return-types">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1781,6 +1781,21 @@ InGroup; def err_qualified_member_of_unrelated : Error< "%q0 is not a member of class %1">; +def ext_member_declared_too_late : ExtWarn< + "name %0 refers to a member of class %1 that has not yet been declared">, + InGroup; +def note_tag_decl_injects_non_member : Note< + "declaration injects non-member %q0; " + "%select{use a standalone forward declaration to introduce a class member|" + "if a member was intended, remove this friend declaration (befriending a " + "member has no effect)}1">; +def note_member_declared_too_late_qualify : Note< + "use a qualified name to refer to member of %0">; +def note_member_declared_too_late : Note< + "member %0 declared here after its point of use">; +def ext_member_declared_too_late_multi_idns : ExtWarn< + "declaration of member %0 after its name has already been used " + "within the definition of class %1">, InGroup; def err_member_function_call_bad_cvr : Error< "'this' argument to member function %0 has type %1, but function is not marked " diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1445,6 +1445,7 @@ Parser &P; bool Popped; Sema::ParsingClassState State; + Scope::EmptyLookupMap EmptyLookups; public: ParsingClassDefinition(Parser &P, Decl *TagOrTemplate, bool TopLevelClass, @@ -1453,9 +1454,22 @@ State(P.PushParsingClass(TagOrTemplate, TopLevelClass, IsInterface)) { } - /// Pop this class of the stack. + void startBody() { + assert(!Popped); + assert(P.getCurScope()->isClassScope()); + P.getCurScope()->enableLookupTracking(&EmptyLookups); + } + + void finishBody() { + assert(!Popped); + assert(P.getCurScope()->isClassScope()); + P.getCurScope()->disableLookupTracking(); + } + + /// Pop this class off the stack. void Pop() { assert(!Popped && "Nested class has already been popped"); + finishBody(); Popped = true; P.PopParsingClass(State); } diff --git a/clang/include/clang/Sema/Lookup.h b/clang/include/clang/Sema/Lookup.h --- a/clang/include/clang/Sema/Lookup.h +++ b/clang/include/clang/Sema/Lookup.h @@ -298,6 +298,12 @@ bool isTemplateNameLookup() const { return TemplateNameLookup; } + /// Sets that this is corresponds to a synthetic name lookup (for example, + /// during typo correction) rather than any source-level syntax. + void setSynthetic() { Synthetic = true; } + + bool isSynthetic() { return Synthetic; } + bool isAmbiguous() const { return getResultKind() == Ambiguous; } @@ -378,6 +384,11 @@ return IDNS; } + /// Override the identifier namespace for this lookup. This should generally + /// not be used; the identifier namespace should instead be inferred from the + /// LookupNameKind. + void setIdentifierNamespace(unsigned IDNS) { this->IDNS = IDNS; } + /// Returns whether these results arose from performing a /// lookup into a class. bool isClassLookup() const { @@ -754,6 +765,11 @@ /// True if we're looking up a template-name. bool TemplateNameLookup = false; + + /// True if this is a synthetic name lookup (one generated internally and not + /// part of the interpretation of the program, such as during typo + /// correction). + bool Synthetic = false; }; /// Consumes visible declarations found when searching for diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h --- a/clang/include/clang/Sema/Scope.h +++ b/clang/include/clang/Sema/Scope.h @@ -14,7 +14,9 @@ #define LLVM_CLANG_SEMA_SCOPE_H #include "clang/AST/Decl.h" +#include "clang/AST/DeclarationName.h" #include "clang/Basic/Diagnostic.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" @@ -136,6 +138,21 @@ CatchScope = 0x1000000, }; + /// A lookup that was performed in this scope and that found nothing here. + struct EmptyLookup { + /// The point of first use. + SourceLocation FirstUseLoc; + /// The Decl::IdentifierNamespace mask for the first use. + unsigned FirstIDNS : 16; + /// The Decl::IdentifierNamespace mask for all uses of this name in this + /// scope. Guaranteed to be a superset of FirstIDNS. + unsigned CumulativeIDNS : 16; + /// Determine whether this represents a lookup that has been attempted and + /// failed (\c true) or a lookup that has never been attempted (\c false). + explicit operator bool() const { return FirstUseLoc.isValid(); } + }; + using EmptyLookupMap = llvm::SmallDenseMap; + private: /// The parent scope for this scope. This is null for the translation-unit /// scope. @@ -208,8 +225,17 @@ /// this scope, or over-defined. The bit is true when over-defined. llvm::PointerIntPair NRVO; + /// DeclarationNames that have been looked up in this scope and not found, + /// for scopes where that is tracked. This is tracked in C++ class scopes in + /// order to diagnose cases where a class member is declared after that + /// identifier has already been used with a different meaning. + EmptyLookupMap *EmptyLookups = nullptr; + void setFlags(Scope *Parent, unsigned F); + void noteEmptyLookupImpl(DeclarationName N, SourceLocation UseLoc, + unsigned IDNS); + public: Scope(Scope *Parent, unsigned ScopeFlags, DiagnosticsEngine &Diag) : ErrorTrap(Diag) { @@ -278,6 +304,45 @@ return PrototypeIndex++; } + /// For a class scope, enable tracking of members that are declared after + /// their names have already been looked up. This is ill-formed by + /// C++ [basic.scope.class]p2. + void enableLookupTracking(EmptyLookupMap *Map) { + assert(!EmptyLookups && "lookup map set twice"); + if (!Map) + return; + + assert((getFlags() & ClassScope) && "not a class scope"); + EmptyLookups = Map; + } + + EmptyLookupMap *disableLookupTracking() { + auto *Map = EmptyLookups; + EmptyLookups = nullptr; + return Map; + } + + /// Note that the given name was looked up within this scope, and nothing was + /// found. + void noteEmptyLookup(DeclarationName N, SourceLocation UseLoc, + unsigned IDNS) { + if (EmptyLookups) + noteEmptyLookupImpl(N, UseLoc, IDNS); + } + + /// Determine whether a lookup for name N in this scope was attempted and + /// found nothing, and if so, what kind of name was looked for and where. + EmptyLookup getEmptyLookupForName(DeclarationName N) { + return EmptyLookups ? EmptyLookups->lookup(N) : EmptyLookup(); + } + + /// Forget about an empty lookup of a given name (to avoid duplicate + /// diagnostics). + void eraseEmptyLookup(DeclarationName N) { + if (EmptyLookups) + EmptyLookups->erase(N); + } + using decl_range = llvm::iterator_range; decl_range decls() const { diff --git a/clang/include/clang/Sema/SemaInternal.h b/clang/include/clang/Sema/SemaInternal.h --- a/clang/include/clang/Sema/SemaInternal.h +++ b/clang/include/clang/Sema/SemaInternal.h @@ -103,6 +103,7 @@ Namespaces(SemaRef.Context, SemaRef.CurContext, SS), EnteringContext(EnteringContext), SearchNamespaces(false) { Result.suppressDiagnostics(); + Result.setSynthetic(); // Arrange for ValidatedCorrections[0] to always be an empty correction. ValidatedCorrections.push_back(TypoCorrection()); } diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -1116,6 +1116,12 @@ TentativeParsingAction Inner(Self); Self.ConsumeAndStoreUntil(EndKind, Toks, true, /*ConsumeFinalToken*/false); Inner.Revert(); + + // Disable lookup tracking; these lookups are going to be reverted, so + // don't count. + for (Scope *S = Self.getCurScope(); S; S = S->getParent()) + if (auto *Map = S->disableLookupTracking()) + OldLookupMaps.push_back({S, Map}); } void RevertAnnotations() { @@ -1132,12 +1138,18 @@ Self.Tok = Toks.front(); } + + for (auto Map : OldLookupMaps) + Map.first->enableLookupTracking(Map.second); } private: Parser &Self; CachedTokens Toks; tok::TokenKind EndKind; + + llvm::SmallVector, 4> + OldLookupMaps; }; /// ConsumeAndStoreInitializer - Consume and store the token at the passed token diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3352,6 +3352,8 @@ BalancedDelimiterTracker T(*this, tok::l_brace); T.consumeOpen(); + ParsingDef.startBody(); + if (TagDecl) Actions.ActOnStartCXXMemberDeclarations(getCurScope(), TagDecl, FinalLoc, IsFinalSpelledSealed, @@ -3390,6 +3392,8 @@ T.getOpenLocation(), T.getCloseLocation(), attrs); + ParsingDef.finishBody(); + // C++11 [class.mem]p2: // Within the class member-specification, the class is regarded as complete // within function bodies, default arguments, exception-specifications, and diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp --- a/clang/lib/Sema/Scope.cpp +++ b/clang/lib/Sema/Scope.cpp @@ -92,6 +92,8 @@ Entity = nullptr; ErrorTrap.reset(); NRVO.setPointerAndInt(nullptr, 0); + assert(!EmptyLookups && + "failed to disable lookup tracking before reusing scope"); } bool Scope::containedInPrototypeScope() const { @@ -118,6 +120,29 @@ Flags |= FlagsToSet; } +void Scope::noteEmptyLookupImpl(DeclarationName N, SourceLocation UseLoc, + unsigned IDNS) { + auto Ret = EmptyLookups->insert({N, {UseLoc, IDNS, IDNS}}); + if (Ret.second) + return; + + // We might have performed lookups with different IDNSes. For example: + // + // struct S { + // A::B *p; // #1 + // A *q; // #2 + // struct A {}; // #3 + // }; + // + // In such cases, preserve the first source location but all of the IDNSes. + // We'll diagnose at the first use location if possible and fall back to not + // giving a use location if the first use was valid (which is rare). + auto &Old = Ret.first->second; + if (Old.FirstUseLoc == UseLoc) + Old.FirstIDNS |= IDNS; + Old.CumulativeIDNS |= IDNS; +} + void Scope::mergeNRVOIntoParent() { if (VarDecl *Candidate = NRVO.getPointer()) { if (isDeclScope(Candidate)) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -612,6 +612,7 @@ // Do a tag name lookup in this scope. LookupResult R(*this, &II, SourceLocation(), LookupTagName); LookupName(R, S, false); + R.setSynthetic(); R.suppressDiagnostics(); if (R.getResultKind() == LookupResult::Found) if (const TagDecl *TD = R.getAsSingle()) { @@ -792,6 +793,7 @@ IdentifierInfo *&Name, SourceLocation NameLoc) { LookupResult R(SemaRef, Name, NameLoc, Sema::LookupTagName); + R.setSynthetic(); SemaRef.LookupParsedName(R, S, &SS); if (TagDecl *Tag = R.getAsSingle()) { StringRef FixItTagName; @@ -1462,6 +1464,220 @@ New->hasAttr()); } +/// Get a nested name specifier suitable to name \p TargetContext from \p +/// CurContext. +/// +/// This is implemented below in terms of +/// TypoCorrectionConsumer::NestedNameSpecifierSet. +static NestedNameSpecifier * +getRelativeNestedNameSpecifier(Sema &SemaRef, Scope *CurScope, + DeclContext *TargetContext) { + llvm::function_ref MakeRec; + auto Make = [&](DeclContext *DC) -> NestedNameSpecifier * { + if (isa(DC)) + return NestedNameSpecifier::GlobalSpecifier(SemaRef.Context); + + // If the DeclContext is a type, we're looking for a way to name that type. + QualType DCType; + if (auto *TD = dyn_cast(DC)) + DCType = SemaRef.Context.getTypeDeclType(TD); + + // If we hit something like a function scope that's not common between the + // current context and the target, we can't reasonably build a nested name + // specifier. + if (!isa(DC) && DCType.isNull()) + return nullptr; + + DeclarationName Name = cast(DC)->getDeclName(); + + // We might be able to refer to this by unqualified name. Try it. + LookupResult R(SemaRef, Name, SourceLocation(), + Sema::LookupNestedNameSpecifierName); + if (Name) { + R.setSynthetic(); + R.suppressDiagnostics(); + SemaRef.LookupName(R, CurScope); + } + + QualType RAsType; + if (auto *TD = R.getAsSingle()) + RAsType = SemaRef.Context.getTypeDeclType(TD); + + NestedNameSpecifier *Prefix = nullptr; + if (R.isSingleResult() && + declaresSameEntity(R.getFoundDecl(), cast(DC))) { + // No prefix: we can name the entity directly in this context. + } else if (!DCType.isNull() && !RAsType.isNull() && + SemaRef.Context.hasSameUnqualifiedType(DCType, RAsType)) { + // No prefix: we can name the type directly in this context. + } else { + Prefix = MakeRec(DC->getLookupParent()); + if (!Prefix) + return nullptr; + + // When naming a class template from an enclosing context, switch from + // the injected-class-name to the template specialization type. + if (!DCType.isNull()) + if (auto *Injected = DCType->getAs()) + DCType = Injected->getInjectedSpecializationType(); + } + + if (auto *ND = dyn_cast(DC)) { + // Don't mention inline namespaces if we can help it. + // FIXME: Do we ever need to mention them for disambiguation? + if (ND->isInline() && Prefix) + return Prefix; + return NestedNameSpecifier::Create(SemaRef.Context, Prefix, ND); + } + + return NestedNameSpecifier::Create(SemaRef.Context, Prefix, + /*TemplateKW=*/false, + DCType.getTypePtr()); + }; + MakeRec = Make; + + return Make(TargetContext); +} + +static Scope *getTagInjectionScope(Scope *S, const LangOptions &LangOpts); + +/// Diagnose the case where we have performed a lookup for the name of a +/// declaration at class scope and found nothing, then later seen a declaration +/// of a class member with the given name. +/// +/// This is ill-formed (no diagnostic required) by [basic.scope.class.p2]. +static void diagnoseEmptyLookupShadow(Sema &SemaRef, Scope *S, NamedDecl *D, + Scope::EmptyLookup Lookup) { + // C++ [basic.scope.class]p2: + // A name N used in a class S shall refer to the same declaration in its + // context and when re-evaluated in the completed scope of S. No diagnostic + // is required for a violation of this rule. + assert(SemaRef.getLangOpts().CPlusPlus && + "empty lookup detection should only be done in C++"); + + // Suppress diagnostics for this if we hit any errors while processing the + // class. For example, if lookup for the name fails entirely, producing a + // duplicate warning is likely not useful. Similarly for a case such as + // 'struct A { f(); };' where we first look up 'f' as a type and then recover + // by injecting it as a member name. + if (S->hasUnrecoverableErrorOccurred() || !D->isCXXClassMember()) + return; + + // This must be a class scope. + CXXRecordDecl *RD = cast(S->getEntity()); + + if (Lookup.FirstUseLoc == D->getLocation()) { + // We looked this up but ended up declaring it instead. Don't count + // this as an empty lookup. This happens for cases like + // auto m = 0; + // in C++98 mode, where we would first look up 'm' as the name of a type, + // and then introduce it as a non-type name, and also for things like + // C (f)(); + // C (f)(int); + // in class 'C', where we look up 'f' to determine if we're declaring a + // constructor. + } else if (D->isInIdentifierNamespace(Lookup.FirstIDNS)) { + SemaRef.Diag(Lookup.FirstUseLoc, diag::ext_member_declared_too_late) + << D->getDeclName() << D->getDeclContext()->getRedeclContext(); + + // Consider suggesting a qualified name. Reconstruct the original + // lookup result to figure out what qualifier to suggest. + LookupResult R(SemaRef, D->getDeclName(), Lookup.FirstUseLoc, + Sema::LookupOrdinaryName); + R.setSynthetic(); + R.suppressDiagnostics(); + R.setIdentifierNamespace(Lookup.FirstIDNS); + SemaRef.LookupName(R, S->getParent()); + + // Ignore declarations lexically within the class (eg, injected tag + // declarations). Those will be invalid too, so don't suggest using a + // qualified name to name them. + auto Filter = R.makeFilter(); + while (Filter.hasNext()) { + NamedDecl *D = Filter.next(); + if (D->getLexicalDeclContext()->getRedeclContext()->Equals(RD)) { + bool FoundReplacement = false; + for (auto *Redecl : D->redecls()) { + if (!D->getLexicalDeclContext()->getRedeclContext()->Equals(RD)) { + Filter.replace(cast(Redecl)); + FoundReplacement = true; + break; + } + } + if (!FoundReplacement) + Filter.erase(); + } + } + Filter.done(); + + // If anything remains, that's likely to be what was intended. + if (!R.empty()) { + NamedDecl *OldD = R.getRepresentativeDecl(); + + // Attempt to form a good fix-it hint describing how to properly name + // the declaration we found earlier. This might fail. + FixItHint Hint; + if (NestedNameSpecifier *NNS = getRelativeNestedNameSpecifier( + SemaRef, S, OldD->getDeclContext()->getRedeclContext())) { + llvm::SmallString<64> Str; + llvm::raw_svector_ostream OS(Str); + PrintingPolicy Policy = SemaRef.Context.getPrintingPolicy(); + Policy.PrintInjectedClassNameWithArguments = false; + NNS->print(OS, Policy); + Hint = FixItHint::CreateInsertion(Lookup.FirstUseLoc, Str); + } + + SemaRef.Diag(OldD->getLocation(), + diag::note_member_declared_too_late_qualify) + << OldD->getDeclContext() << Hint; + } + + if (R.empty() && (Lookup.FirstIDNS == Decl::IDNS_Type) && isa(D)) { + // This was a failed tag lookup, and has been shadowed by a tag + // declaration. It's very likely that the programmer believed that the + // tag reference would declare the tag as a member. For example: + // struct A { + // friend class X; + // class X {}; + // }; + // Look for the injected tag declaration to check that this is what + // happened. + LookupResult TagR(SemaRef, D->getDeclName(), Lookup.FirstUseLoc, + Sema::LookupTagName, Sema::ForVisibleRedeclaration); + TagR.setSynthetic(); + TagR.suppressDiagnostics(); + SemaRef.LookupName(TagR, getTagInjectionScope(S, SemaRef.getLangOpts())); + + if (auto *FoundTag = TagR.getAsSingle()) { + for (auto *Redecl : FoundTag->redecls()) { + if (Redecl->getLocation() == Lookup.FirstUseLoc) { + SemaRef.Diag(Lookup.FirstUseLoc, + diag::note_tag_decl_injects_non_member) + << Redecl << !!Redecl->getFriendObjectKind(); + break; + } + } + } + } + + SemaRef.Diag(D->getLocation(), diag::note_member_declared_too_late) << D; + } else if (D->isInIdentifierNamespace(Lookup.CumulativeIDNS)) { + // The first use location was a valid use of an enclosing name, but some + // later use would have been interpreted as the class member. + SemaRef.Diag(D->getLocation(), + diag::ext_member_declared_too_late_multi_idns) + << D << D->getDeclContext()->getRedeclContext(); + } else { + // This declaration does not actually shadow the prior uses. + return; + } + + // Ensure we don't issue more diagnostics if the name is redeclared in + // this class or declared in an enclosing class. + for (Scope *InnerS = S; InnerS; InnerS = InnerS->getParent()) + InnerS->eraseEmptyLookup(D->getDeclName()); +} + /// Add this decl to the scope shadowed decl chains. void Sema::PushOnScopeChains(NamedDecl *D, Scope *S, bool AddToContext) { // Move up the scope chain until we find the nearest enclosing @@ -1499,6 +1715,11 @@ } } + // If we're tracking empty lookups in this context, see if we already tried + // and failed to look for this name, and diagnose if so. + if (Scope::EmptyLookup Lookup = S->getEmptyLookupForName(D->getDeclName())) + diagnoseEmptyLookupShadow(*this, S, D, Lookup); + S->AddDecl(D); if (isa(D) && !cast(D)->isGnuLocal()) { @@ -7685,6 +7906,7 @@ LookupResult R(*this, D->getDeclName(), D->getLocation(), Sema::LookupOrdinaryName, Sema::ForVisibleRedeclaration); + R.setSynthetic(); LookupName(R, S); if (NamedDecl *ShadowedDecl = getShadowedDeclaration(D, R)) CheckShadow(D, ShadowedDecl, R); @@ -8247,6 +8469,7 @@ IsLocalFriend ? Sema::LookupLocalFriendName : Sema::LookupOrdinaryName, Sema::ForVisibleRedeclaration); + Prev.setSynthetic(); NewFD->setInvalidDecl(); if (IsLocalFriend) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -13606,6 +13606,7 @@ "__builtin_memcpy"; LookupResult R(S, &S.Context.Idents.get(MemCpyName), Loc, Sema::LookupOrdinaryName); + R.setSynthetic(); S.LookupName(R, S.TUScope, true); FunctionDecl *MemCpy = R.getAsSingle(); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7919,6 +7919,7 @@ const TypoCorrection &TC) { LookupResult R(SemaRef, Consumer.getLookupResult().getLookupNameInfo(), Consumer.getLookupResult().getLookupKind()); + R.setSynthetic(); const CXXScopeSpec *SS = Consumer.getSS(); CXXScopeSpec NewSS; diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -1295,6 +1295,12 @@ return false; } + // If we've just searched a lookup-tracking scope and found nothing, make a + // note that declaring a new name in this scope could change the lookup + // result. + if (R.empty() && !R.isForRedeclaration() && !R.isSynthetic()) + S->noteEmptyLookup(Name, R.getNameLoc(), R.getIdentifierNamespace()); + if (DeclContext *Ctx = S->getLookupEntity()) { DeclContext *OuterCtx = findOuterContext(S); for (; Ctx && !Ctx->Equals(OuterCtx); Ctx = Ctx->getLookupParent()) { @@ -2456,6 +2462,8 @@ CXXRecordDecl *RD = cast( BaseSpec.getType()->castAs()->getDecl()); LookupResult Result(*this, R.getLookupNameInfo(), R.getLookupKind()); + if (R.isSynthetic()) + Result.setSynthetic(); Result.setBaseObjectType(Context.getRecordType(Class)); LookupQualifiedName(Result, RD); @@ -5458,6 +5466,7 @@ void Sema::ActOnPragmaDump(Scope *S, SourceLocation IILoc, IdentifierInfo *II) { DeclarationNameInfo Name(II, IILoc); LookupResult R(*this, Name, LookupAnyName, Sema::NotForRedeclaration); + R.setSynthetic(); R.suppressDiagnostics(); R.setHideTags(false); LookupName(R, S); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -448,6 +448,13 @@ } if (SS.isEmpty() && (ObjectType.isNull() || Found.empty())) { + if (IsDependent) { + // FIXME: Conservatively treat this fallback lookup as synthetic. We + // don't know whether we will actually want to look in the scope until + // instantiation. + Found.setSynthetic(); + } + // C++ [basic.lookup.classref]p1: // In a class member access expression (5.2.5), if the . or -> token is // immediately followed by an identifier followed by a <, the diff --git a/clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp b/clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp @@ -0,0 +1,218 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s + +// A name N used in a class S shall refer to the same declaration in its +// context and when re-evaluated in the completed scope of S. + +enum Enum {}; // expected-note {{use a qualified name}} +enum Enum2 {}; // expected-note {{use a qualified name}} +class Class {}; // expected-note 4{{use a qualified name}} +class Class2 {}; // expected-note 2{{use a qualified name}} +class Class3 {}; // expected-note {{use a qualified name}} +using Typedef = int; // expected-note {{use a qualified name}} +using Typedef2 = int; // expected-note {{use a qualified name}} +int Function(); // expected-note {{use a qualified name}} + +struct UseInSelf { + Class * // expected-warning {{name 'Class' refers to a member of class 'UseInSelf' that has not yet been declared}} + Class; // expected-note {{declared here after its point of use}} +}; + +struct Friends { + friend class FriendClass; // expected-warning {{name 'FriendClass' refers to a member of class 'Friends' that has not yet been declared}} expected-note {{injects non-member 'FriendClass'; if a member was intended, remove this friend declaration (befriending a member has no effect)}} + class FriendClass; // expected-note {{member 'FriendClass' declared here after its point of use}} + + friend class Class; // expected-warning {{not yet been declared}} + class Class; // expected-note {{declared here}} + + friend Class2; // expected-warning {{not yet been declared}} + class Class2; // expected-note {{declared here}} + + friend enum Enum; // expected-warning {{not yet been declared}} + enum class Enum : int; // expected-note {{declared here}} + + friend Enum2; // expected-warning {{not yet been declared}} + enum class Enum2 : int; // expected-note {{declared here}} + + friend int FriendFunction(); + int FriendFunction(); + + friend int Function(); + int Function(); + + friend Typedef; // expected-warning {{not yet been declared}} + using Typedef = int; // expected-note {{declared here}} + + friend Typedef2; // expected-warning {{not yet been declared}} + class Typedef2; // expected-note {{declared here}} +}; + +struct Elaborated { + class Class *p1; // expected-warning {{not yet been declared}} + Class *q1; + class Class {}; // expected-note {{declared here}} + + class Class2 *p2; // expected-warning {{not yet been declared}} + Class2 *q2; + using Class2 = int; // expected-note {{declared here}} + + class Class3 *p3; // expected-warning {{not yet been declared}} + using Class3 = int; // expected-note {{declared here}} +}; + +struct HasMember { // expected-note 2{{use a qualified name}} + using Member = int; +}; + +struct NestedNameSpecifierLookup1 { + // Here, it would not be OK to warn on the first usage, because the lookup + // for HasMember would ignore the member below. + HasMember::Member *p; + HasMember *q; // FIXME: would be nice to indicate this usage is erroneous + int HasMember; // expected-warning {{declaration of member 'HasMember' after its name has already been used}} +}; + +struct NestedNameSpecifierLookup2 { + HasMember::Member *p; // expected-warning {{not yet been declared}} + HasMember *q; + struct HasMember; // expected-note {{declared here}} +}; + +struct NestedNameSpecifierLookup3 { + HasMember::Member *p; // OK, lookup ignores non-types. + int HasMember; +}; + +struct NestedNameSpecifierLookup4 { + HasMember::Member *p; // expected-warning {{not yet been declared}} + struct HasMember; // expected-note {{declared here}} +}; + +struct NestedOuter { + struct NestedMiddle { + struct Inner { + Class *p; // expected-warning {{refers to a member of class 'NestedOuter::NestedMiddle' that has not yet been declared}} + }; + struct Class {}; // expected-note {{declared here}} + }; + struct Class {}; +}; + +struct Enumerators { + int arr[sizeof(Function())]; // expected-warning {{refers to a member of class 'Enumerators'}} + enum { + Function = 1 // expected-note {{here}} + }; +}; + +struct Operators {}; +int operator+(Operators, Operators); +struct OperatorMembers { + int arr[sizeof(Operators() + Operators())]; + int operator+(Operators); // OK, no conflict with the above + friend int operator+(Operators, Operators); // OK, no conflict with the above +}; + +namespace Namespace {} + +namespace A { +struct Namespace {}; + +struct UsingDirective1 { + constexpr static int a = [] { using namespace Namespace; return 0; }(); + struct Namespace *ns; + int Namespace; // OK, does not hide either use. +}; + +// We get a worse warning here (with no use location) because neither use +// below has a strictly more permissive IDNS. +// FIXME: We could improve the diagnostic here: there's no way for a name +// found by 'using namespace' to be hidden by a class-scope declaration. +struct UsingDirective2 { + constexpr static int a = [] { using namespace Namespace; return 0; }(); + struct Namespace *ns; + struct Namespace {}; // expected-warning {{declaration of member 'Namespace' after its name has already been used within the definition of class 'A::UsingDirective2'}} +}; + +struct UsingDirective3 { + constexpr static int a = [] { using namespace Namespace; return 0; }(); + struct Namespace *ns; + using Namespace = int; // expected-warning {{declaration of member 'Namespace' after its name has already been used within the definition of class 'A::UsingDirective3'}} +}; +} // namespace A + +struct DeclaredTooLate { + // FIXME: In this case it would be prefereable to diagnose that NewUniqueType + // has not yet been declared. + // + // We could in principle get this result by delaying the "undeclared" + // diagnostic until the end of the class (or until we want to produce another + // error, which might be a follow-on error from that error). It's not clear + // that this would be worth the complexity. + NewUniqueType f(); // expected-error {{unknown type name}} + struct NewUniqueType {}; + + static inline int a = unique_member_function_name(); // expected-error {{undeclared identifier}} + static int unique_member_function_name(); +}; + +struct Undeclared { + // Don't also produce a "declared after use" warning here, even though we + // look up 'f' as a type before we declare it as a member. + f(); // expected-error {{requires a type specifier}} +}; + +struct IgnoringNonTypes { + template + auto f(T t) -> decltype(t.template operator()()); + template + auto f(T t) -> decltype(t.template g()); + template + auto f(T t) -> decltype(t.template T()); + template + auto f(T t) -> decltype(t.template U::f()); + + template + void operator()(); + template + void g(); + template + struct T {}; + template + struct U {}; +}; + +namespace TagShadow { +template +struct A { + struct X {}; // expected-note 2{{use a qualified name to refer to member of 'A'}} + struct B { + X // expected-warning {{member of class 'TagShadow::A::B' that has not yet been declared}} + *X; // expected-note {{declared here after its point of use}} + }; + struct C { + using A = int; + X // expected-warning {{not yet been declared}} + *X; // expected-note {{declared here}} + }; + friend class Z; // expected-warning {{not yet been declared}} expected-note {{declaration injects non-member 'TagShadow::Z'; if a member was intended, remove}} + class Z {}; // expected-note {{declared here}} +}; +struct W { + // FIXME: We should point out that this injects a non-member too, but this + // doesn't inject a tag declaration because 'TagShadow::Z' was already + // declared by 'A'. + friend class Z; // expected-warning {{not yet been declared}} + class Z {}; // expected-note {{declared here}} +}; + +struct QB; // expected-note {{use a qualified name}} +struct Q { + struct QA *p; // tag lookup, expected-warning {{not yet been declared}} expected-note {{use a standalone forward declaration}} + int QA::*q; // nested name specifier lookup + int QB::*r; // nested name specifier lookup, expected-warning {{not yet been declared}} + struct QB *s; // tag lookup + + struct QA {}; // expected-note {{declared here after its point of use}} + struct QB {}; // expected-note {{declared here after its point of use}} +}; +} // namespace TagShadow diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp --- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp +++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp @@ -2,7 +2,8 @@ struct S { typedef struct A {} A; // expected-note {{previous definition is here}} - typedef struct B B; + typedef struct B // expected-warning {{name 'B' refers to a member of class 'S' that has not yet been declared}} + B; // expected-note {{declared here}} typedef A A; // expected-error {{redefinition of 'A'}} struct C { }; diff --git a/clang/test/CXX/drs/dr2xx.cpp b/clang/test/CXX/drs/dr2xx.cpp --- a/clang/test/CXX/drs/dr2xx.cpp +++ b/clang/test/CXX/drs/dr2xx.cpp @@ -951,11 +951,14 @@ } namespace dr283 { // dr283: yes - template // expected-note 2{{here}} - struct S { - friend class T; // expected-error {{shadows}} - class T; // expected-error {{shadows}} - }; +template // expected-note {{here}} +struct S1 { + friend class T; // expected-error {{shadows}} +}; +template // expected-note {{here}} +struct S2 { + class T; // expected-error {{shadows}} +}; } namespace dr284 { // dr284: no diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp --- a/clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp +++ b/clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp @@ -149,14 +149,15 @@ class wrap { public: - template - auto operator()(Args&&... args) const -> decltype(wrapped(args...)) // expected-note{{candidate template ignored: substitution failure [with Args = ]: member 'wrapped' used before its declaration}} + template + auto operator()(Args &&...args) const -> decltype(wrapped(args...)) // expected-note{{candidate template ignored: substitution failure [with Args = ]: member 'wrapped' used before its declaration}} + // expected-warning@-1 {{name 'wrapped' refers to a member of class 'wrap' that has not yet been declared}} { return wrapped(args...); } - + private: - F wrapped; + F wrapped; // expected-note {{declared here}} }; void test(wrap w) { diff --git a/clang/test/Parser/cxx-ambig-init-templ.cpp b/clang/test/Parser/cxx-ambig-init-templ.cpp --- a/clang/test/Parser/cxx-ambig-init-templ.cpp +++ b/clang/test/Parser/cxx-ambig-init-templ.cpp @@ -94,16 +94,16 @@ namespace NoAnnotationTokens { template struct Bool { Bool(int); }; - static const bool in_class = false; + static const bool in_class = false; // expected-note {{use a qualified name to refer to member of namespace 'NoAnnotationTokens'}} struct Test { // Check we don't keep around a Bool annotation token here. int f(Bool = X >(0)); // But it's OK if we do here. - int g(Bool = Z = Bool(0)); + int g(Bool = Z < Y, Bool = Bool(0)); // expected-warning {{not yet been declared}} - static const bool in_class = true; + static const bool in_class = true; // expected-note {{declared here}} template using X = U; static const int Y = 0, Z = 0; }; diff --git a/clang/test/Parser/cxx0x-override-control-keywords.cpp b/clang/test/Parser/cxx0x-override-control-keywords.cpp --- a/clang/test/Parser/cxx0x-override-control-keywords.cpp +++ b/clang/test/Parser/cxx0x-override-control-keywords.cpp @@ -1,5 +1,4 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s -// expected-no-diagnostics struct Base { virtual void override(); @@ -14,9 +13,10 @@ virtual void override() override { } }; -struct override; +struct override; // expected-note {{use a qualified name to refer to member of the global namespace}} struct Base2 { - virtual override override(int override); + virtual override // expected-warning {{not yet been declared}} + override(int override); // expected-note {{here}} }; struct A : Base2 { diff --git a/clang/test/SemaTemplate/alias-templates.cpp b/clang/test/SemaTemplate/alias-templates.cpp --- a/clang/test/SemaTemplate/alias-templates.cpp +++ b/clang/test/SemaTemplate/alias-templates.cpp @@ -41,7 +41,7 @@ template struct thing { typedef T inner; typedef ptr inner_ptr; - typedef traits> traits_type; + typedef X::traits> traits_type; template using rebind = thing; diff --git a/clang/test/SemaTemplate/deduction-crash.cpp b/clang/test/SemaTemplate/deduction-crash.cpp --- a/clang/test/SemaTemplate/deduction-crash.cpp +++ b/clang/test/SemaTemplate/deduction-crash.cpp @@ -19,7 +19,7 @@ template struct state_machine { - typedef aaa::ae aaa; + typedef ::aaa::ae aaa; int start() { ant(0); diff --git a/clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp b/clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp --- a/clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp +++ b/clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp @@ -64,16 +64,18 @@ } namespace core_19754_example { - template T declval() noexcept; +namespace N { +template +T declval() noexcept; - template()))> - struct is_movable { static const bool value = true; }; +template ()))> +struct is_movable { static const bool value = true; }; - template - struct wrap { - T val; - void irrelevant(wrap &p) noexcept(is_movable::value); - }; +template +struct wrap { + T val; + void irrelevant(wrap &p) noexcept(is_movable::value); +}; template struct base { @@ -93,13 +95,14 @@ }; struct types { - typedef base base; - typedef type1 type1; - typedef type2 type2; + typedef N::base base; + typedef N::type1 type1; + typedef N::type2 type2; }; base val = base(); -} + } // namespace N + } // namespace core_19754_example namespace pr9485 { template void f1(T) throw(typename T::exception); // expected-note {{candidate}} diff --git a/clang/test/SemaTemplate/ms-delayed-default-template-args.cpp b/clang/test/SemaTemplate/ms-delayed-default-template-args.cpp --- a/clang/test/SemaTemplate/ms-delayed-default-template-args.cpp +++ b/clang/test/SemaTemplate/ms-delayed-default-template-args.cpp @@ -24,10 +24,11 @@ namespace test_inner_class_template { struct Outer { template // expected-warning {{using the undeclared type 'Baz' as a default template argument is a Microsoft extension}} + // expected-warning@-1 {{not yet been declared}} struct Foo { static_assert(sizeof(T) == 4, "should get int, not double"); }; - typedef int Baz; + typedef int Baz; // expected-note {{declared here}} }; typedef double Baz; template struct Outer::Foo<>;