DR458: Search template parameter scopes in the right order.

C++ unqualified name lookup searches template parameter scopes
immediately after finishing searching the entity the parameters belong
to. (Eg, for a class template, you search the template parameter scope
after looking in that class template and its base classes and before
looking in the scope containing the class template.) This is complicated
by the fact that scope lookup within a template parameter scope looks in
a different sequence of places prior to reaching the end of the
declarator-id in the template declaration.

We used to approximate the proper lookup rule with a hack in the scope /
decl context walk inside name lookup. Now we instead compute the lookup
parent for each template parameter scope. This gets the right answer and
as a bonus is substantially simpler and more uniform.

In order to get this right, we now make sure to enter a distinct Scope
for each template parameter scope. (The fact that we didn't before was
already a bug, but not really observable most of the time, since
template parameters can't shadow each other.)
This commit is contained in:
Richard Smith 2020-06-16 16:55:56 -07:00
parent 1b8125b041
commit 237c2a23b6
12 changed files with 204 additions and 158 deletions

View File

@ -1088,6 +1088,31 @@ public:
}
};
/// Introduces zero or more scopes for parsing. The scopes will all be exited
/// when the object is destroyed.
class MultiParseScope {
Parser &Self;
unsigned NumScopes = 0;
MultiParseScope(const MultiParseScope&) = delete;
public:
MultiParseScope(Parser &Self) : Self(Self) {}
void Enter(unsigned ScopeFlags) {
Self.EnterScope(ScopeFlags);
++NumScopes;
}
void Exit() {
while (NumScopes) {
Self.ExitScope();
--NumScopes;
}
}
~MultiParseScope() {
Exit();
}
};
/// EnterScope - Start a new scope.
void EnterScope(unsigned ScopeFlags);
@ -3240,7 +3265,7 @@ private:
DeclaratorContext Context, const ParsedTemplateInfo &TemplateInfo,
ParsingDeclRAIIObject &DiagsFromParams, SourceLocation &DeclEnd,
ParsedAttributes &AccessAttrs, AccessSpecifier AS = AS_none);
bool ParseTemplateParameters(unsigned Depth,
bool ParseTemplateParameters(MultiParseScope &TemplateScopes, unsigned Depth,
SmallVectorImpl<NamedDecl *> &TemplateParams,
SourceLocation &LAngleLoc,
SourceLocation &RAngleLoc);

View File

@ -322,8 +322,21 @@ public:
/// declared in.
bool isDeclScope(const Decl *D) const { return DeclsInScope.count(D) != 0; }
DeclContext *getEntity() const { return Entity; }
void setEntity(DeclContext *E) { Entity = E; }
/// Get the entity corresponding to this scope.
DeclContext *getEntity() const {
return isTemplateParamScope() ? nullptr : Entity;
}
/// Get the DeclContext in which to continue unqualified lookup after a
/// lookup in this scope.
DeclContext *getLookupEntity() const { return Entity; }
void setEntity(DeclContext *E) {
assert(!isTemplateParamScope() &&
"entity associated with template param scope");
Entity = E;
}
void setLookupEntity(DeclContext *E) { Entity = E; }
/// Determine whether any unrecoverable errors have occurred within this
/// scope. Note that this may return false even if the scope contains invalid

View File

@ -241,7 +241,7 @@ TemplateDecl *Decl::getDescribedTemplate() const {
}
bool Decl::isTemplated() const {
// A declaration is dependent if it is a template or a template pattern, or
// A declaration is templated if it is a template or a template pattern, or
// is within (lexcially for a friend, semantically otherwise) a dependent
// context.
// FIXME: Should local extern declarations be treated like friends?

View File

@ -1258,17 +1258,16 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer(
};
// FIXME: Consider allowing this as an extension for GCC compatibiblity.
const bool HasExplicitTemplateParams = Tok.is(tok::less);
ParseScope TemplateParamScope(this, Scope::TemplateParamScope,
/*EnteredScope=*/HasExplicitTemplateParams);
if (HasExplicitTemplateParams) {
MultiParseScope TemplateParamScope(*this);
if (Tok.is(tok::less)) {
Diag(Tok, getLangOpts().CPlusPlus20
? diag::warn_cxx17_compat_lambda_template_parameter_list
: diag::ext_lambda_template_parameter_list);
SmallVector<NamedDecl*, 4> TemplateParams;
SourceLocation LAngleLoc, RAngleLoc;
if (ParseTemplateParameters(CurTemplateDepthTracker.getDepth(),
if (ParseTemplateParameters(TemplateParamScope,
CurTemplateDepthTracker.getDepth(),
TemplateParams, LAngleLoc, RAngleLoc)) {
Actions.ActOnLambdaError(LambdaBeginLoc, getCurScope());
return ExprError();

View File

@ -67,8 +67,7 @@ Decl *Parser::ParseTemplateDeclarationOrSpecialization(
assert(Tok.isOneOf(tok::kw_export, tok::kw_template) &&
"Token does not start a template declaration.");
// Enter template-parameter scope.
ParseScope TemplateParmScope(this, Scope::TemplateParamScope);
MultiParseScope TemplateParamScopes(*this);
// Tell the action that names should be checked in the context of
// the declaration to come.
@ -116,7 +115,8 @@ Decl *Parser::ParseTemplateDeclarationOrSpecialization(
// Parse the '<' template-parameter-list '>'
SourceLocation LAngleLoc, RAngleLoc;
SmallVector<NamedDecl*, 4> TemplateParams;
if (ParseTemplateParameters(CurTemplateDepthTracker.getDepth(),
if (ParseTemplateParameters(TemplateParamScopes,
CurTemplateDepthTracker.getDepth(),
TemplateParams, LAngleLoc, RAngleLoc)) {
// Skip until the semi-colon or a '}'.
SkipUntil(tok::r_brace, StopAtSemi | StopBeforeMatch);
@ -150,9 +150,6 @@ Decl *Parser::ParseTemplateDeclarationOrSpecialization(
TemplateParams, RAngleLoc, OptionalRequiresClauseConstraintER.get()));
} while (Tok.isOneOf(tok::kw_export, tok::kw_template));
unsigned NewFlags = getCurScope()->getFlags() & ~Scope::TemplateParamScope;
ParseScopeFlags TemplateScopeFlags(this, NewFlags, isSpecialization);
// Parse the actual template declaration.
if (Tok.is(tok::kw_concept))
return ParseConceptDefinition(
@ -430,8 +427,9 @@ Parser::ParseConceptDefinition(const ParsedTemplateInfo &TemplateInfo,
///
/// \returns true if an error occurred, false otherwise.
bool Parser::ParseTemplateParameters(
unsigned Depth, SmallVectorImpl<NamedDecl *> &TemplateParams,
SourceLocation &LAngleLoc, SourceLocation &RAngleLoc) {
MultiParseScope &TemplateScopes, unsigned Depth,
SmallVectorImpl<NamedDecl *> &TemplateParams, SourceLocation &LAngleLoc,
SourceLocation &RAngleLoc) {
// Get the template parameter list.
if (!TryConsumeToken(tok::less, LAngleLoc)) {
Diag(Tok.getLocation(), diag::err_expected_less_after) << "template";
@ -440,8 +438,11 @@ bool Parser::ParseTemplateParameters(
// Try to parse the template parameter list.
bool Failed = false;
if (!Tok.is(tok::greater) && !Tok.is(tok::greatergreater))
// FIXME: Missing greatergreatergreater support.
if (!Tok.is(tok::greater) && !Tok.is(tok::greatergreater)) {
TemplateScopes.Enter(Scope::TemplateParamScope);
Failed = ParseTemplateParameterList(Depth, TemplateParams);
}
if (Tok.is(tok::greatergreater)) {
// No diagnostic required here: a template-parameter-list can only be
@ -850,9 +851,9 @@ Parser::ParseTemplateTemplateParameter(unsigned Depth, unsigned Position) {
SmallVector<NamedDecl*,8> TemplateParams;
SourceLocation LAngleLoc, RAngleLoc;
{
ParseScope TemplateParmScope(this, Scope::TemplateParamScope);
if (ParseTemplateParameters(Depth + 1, TemplateParams, LAngleLoc,
RAngleLoc)) {
MultiParseScope TemplateParmScope(*this);
if (ParseTemplateParameters(TemplateParmScope, Depth + 1, TemplateParams,
LAngleLoc, RAngleLoc)) {
return nullptr;
}
}
@ -1630,7 +1631,7 @@ void Parser::ParseLateTemplatedFuncDef(LateParsedTemplate &LPT) {
Sema::ContextRAII GlobalSavedContext(
Actions, Actions.Context.getTranslationUnitDecl());
SmallVector<ParseScope*, 4> TemplateParamScopeStack;
MultiParseScope Scopes(*this);
// Get the list of DeclContexts to reenter. For inline methods, we only want
// to push the DeclContext of the outermost class. This matches the way the
@ -1655,13 +1656,12 @@ void Parser::ParseLateTemplatedFuncDef(LateParsedTemplate &LPT) {
// Reenter template scopes from outermost to innermost.
for (ContainingDC CDC : reverse(DeclContextsToReenter)) {
TemplateParamScopeStack.push_back(
new ParseScope(this, Scope::TemplateParamScope));
Scopes.Enter(Scope::TemplateParamScope);
unsigned NumParamLists = Actions.ActOnReenterTemplateScope(
getCurScope(), cast<Decl>(CDC.getDC()));
CurTemplateDepthTracker.addDepth(NumParamLists);
if (CDC.shouldPushDC()) {
TemplateParamScopeStack.push_back(new ParseScope(this, Scope::DeclScope));
Scopes.Enter(Scope::DeclScope);
Actions.PushDeclContext(Actions.getCurScope(), CDC.getDC());
}
}
@ -1709,13 +1709,6 @@ void Parser::ParseLateTemplatedFuncDef(LateParsedTemplate &LPT) {
} else
Actions.ActOnFinishFunctionBody(LPT.D, nullptr);
}
// Exit scopes.
FnScope.Exit();
SmallVectorImpl<ParseScope *>::reverse_iterator I =
TemplateParamScopeStack.rbegin();
for (; I != TemplateParamScopeStack.rend(); ++I)
delete *I;
}
/// Lex a delayed template function for late parsing.

View File

@ -1358,6 +1358,50 @@ void Sema::EnterDeclaratorContext(Scope *S, DeclContext *DC) {
CurContext = DC;
S->setEntity(DC);
if (!S->getParent()->isTemplateParamScope())
return;
// Also set the corresponding entities for all immediately-enclosing template
// parameter scopes.
//
// C++20 [temp.local]p7:
// In the definition of a member of a class template that appears outside
// of the class template definition, the name of a member of the class
// template hides the name of a template-parameter of any enclosing class
// templates (but not a template-parameter of the member if the member is a
// class or function template).
// C++20 [temp.local]p9:
// In the definition of a class template or in the definition of a member
// of such a template that appears outside of the template definition, for
// each non-dependent base class (13.8.2.1), if the name of the base class
// or the name of a member of the base class is the same as the name of a
// template-parameter, the base class name or member name hides the
// template-parameter name (6.4.10).
//
// This means that a template parameter scope should be searched immediately
// after searching the DeclContext for which it is a template parameter
// scope. For example, for
// template<typename T> template<typename U> template<typename V>
// void N::A<T>::B<U>::f(...)
// we search V then B<U> (and base classes) then U then A<T> (and base
// classes) then T then N then ::.
unsigned ScopeDepth = getTemplateDepth(S);
for (Scope *OuterS = S->getParent(); OuterS && OuterS->isTemplateParamScope();
OuterS = OuterS->getParent(), --ScopeDepth) {
auto *SearchDCAfterScope = DC;
for (; DC; DC = DC->getLookupParent()) {
if (auto *TD = cast<Decl>(DC)->getDescribedTemplate()) {
unsigned DCDepth = TD->getTemplateParameters()->getDepth() + 1;
if (DCDepth > ScopeDepth)
continue;
if (ScopeDepth == DCDepth)
SearchDCAfterScope = DC = DC->getLookupParent();
break;
}
}
OuterS->setLookupEntity(SearchDCAfterScope);
}
}
void Sema::ExitDeclaratorContext(Scope *S) {

View File

@ -1153,73 +1153,14 @@ static bool isNamespaceOrTranslationUnitScope(Scope *S) {
return false;
}
// Find the next outer declaration context from this scope. This
// routine actually returns the semantic outer context, which may
// differ from the lexical context (encoded directly in the Scope
// stack) when we are parsing a member of a class template. In this
// case, the second element of the pair will be true, to indicate that
// name lookup should continue searching in this semantic context when
// it leaves the current template parameter scope.
static std::pair<DeclContext *, bool> findOuterContext(Scope *S) {
DeclContext *DC = S->getEntity();
DeclContext *Lexical = nullptr;
for (Scope *OuterS = S->getParent(); OuterS;
OuterS = OuterS->getParent()) {
if (OuterS->getEntity()) {
Lexical = OuterS->getEntity();
break;
}
}
// C++ [temp.local]p8:
// In the definition of a member of a class template that appears
// outside of the namespace containing the class template
// definition, the name of a template-parameter hides the name of
// a member of this namespace.
//
// Example:
//
// namespace N {
// class C { };
//
// template<class T> class B {
// void f(T);
// };
// }
//
// template<class C> void N::B<C>::f(C) {
// C b; // C is the template parameter, not N::C
// }
//
// In this example, the lexical context we return is the
// TranslationUnit, while the semantic context is the namespace N.
if (!Lexical || !DC || !S->getParent() ||
!S->getParent()->isTemplateParamScope())
return std::make_pair(Lexical, false);
// Find the outermost template parameter scope.
// For the example, this is the scope for the template parameters of
// template<class C>.
Scope *OutermostTemplateScope = S->getParent();
while (OutermostTemplateScope->getParent() &&
OutermostTemplateScope->getParent()->isTemplateParamScope())
OutermostTemplateScope = OutermostTemplateScope->getParent();
// Find the namespace context in which the original scope occurs. In
// the example, this is namespace N.
DeclContext *Semantic = DC;
while (!Semantic->isFileContext())
Semantic = Semantic->getParent();
// Find the declaration context just outside of the template
// parameter scope. This is the context in which the template is
// being lexically declaration (a namespace context). In the
// example, this is the global scope.
if (Lexical->isFileContext() && !Lexical->Equals(Semantic) &&
Lexical->Encloses(Semantic))
return std::make_pair(Semantic, true);
return std::make_pair(Lexical, false);
/// Find the outer declaration context from this scope. This indicates the
/// context that we should search up to (exclusive) before considering the
/// parent of the specified scope.
static DeclContext *findOuterContext(Scope *S) {
for (Scope *OuterS = S->getParent(); OuterS; OuterS = OuterS->getParent())
if (DeclContext *DC = OuterS->getLookupEntity())
return DC;
return nullptr;
}
namespace {
@ -1286,13 +1227,11 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
UnqualUsingDirectiveSet UDirs(*this);
bool VisitedUsingDirectives = false;
bool LeftStartingScope = false;
DeclContext *OutsideOfTemplateParamDC = nullptr;
// When performing a scope lookup, we want to find local extern decls.
FindLocalExternScope FindLocals(R);
for (; S && !isNamespaceOrTranslationUnitScope(S); S = S->getParent()) {
DeclContext *Ctx = S->getEntity();
bool SearchNamespaceScope = true;
// Check whether the IdResolver has anything in this scope.
for (; I != IEnd && S->isDeclScope(*I); ++I) {
@ -1324,7 +1263,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
if (!SearchNamespaceScope) {
R.resolveKind();
if (S->isClassScope())
if (CXXRecordDecl *Record = dyn_cast_or_null<CXXRecordDecl>(Ctx))
if (CXXRecordDecl *Record =
dyn_cast_or_null<CXXRecordDecl>(S->getEntity()))
R.setNamingClass(Record);
return true;
}
@ -1338,24 +1278,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
return false;
}
if (!Ctx && S->isTemplateParamScope() && OutsideOfTemplateParamDC &&
S->getParent() && !S->getParent()->isTemplateParamScope()) {
// We've just searched the last template parameter scope and
// found nothing, so look into the contexts between the
// lexical and semantic declaration contexts returned by
// findOuterContext(). This implements the name lookup behavior
// of C++ [temp.local]p8.
Ctx = OutsideOfTemplateParamDC;
OutsideOfTemplateParamDC = nullptr;
}
if (Ctx) {
DeclContext *OuterCtx;
bool SearchAfterTemplateScope;
std::tie(OuterCtx, SearchAfterTemplateScope) = findOuterContext(S);
if (SearchAfterTemplateScope)
OutsideOfTemplateParamDC = OuterCtx;
if (DeclContext *Ctx = S->getLookupEntity()) {
DeclContext *OuterCtx = findOuterContext(S);
for (; Ctx && !Ctx->Equals(OuterCtx); Ctx = Ctx->getLookupParent()) {
// We do not directly look into transparent contexts, since
// those entities will be found in the nearest enclosing
@ -1480,25 +1404,9 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
return true;
}
DeclContext *Ctx = S->getEntity();
if (!Ctx && S->isTemplateParamScope() && OutsideOfTemplateParamDC &&
S->getParent() && !S->getParent()->isTemplateParamScope()) {
// We've just searched the last template parameter scope and
// found nothing, so look into the contexts between the
// lexical and semantic declaration contexts returned by
// findOuterContext(). This implements the name lookup behavior
// of C++ [temp.local]p8.
Ctx = OutsideOfTemplateParamDC;
OutsideOfTemplateParamDC = nullptr;
}
DeclContext *Ctx = S->getLookupEntity();
if (Ctx) {
DeclContext *OuterCtx;
bool SearchAfterTemplateScope;
std::tie(OuterCtx, SearchAfterTemplateScope) = findOuterContext(S);
if (SearchAfterTemplateScope)
OutsideOfTemplateParamDC = OuterCtx;
DeclContext *OuterCtx = findOuterContext(S);
for (; Ctx && !Ctx->Equals(OuterCtx); Ctx = Ctx->getLookupParent()) {
// We do not directly look into transparent contexts, since
// those entities will be found in the nearest enclosing
@ -3995,14 +3903,12 @@ private:
}
}
// FIXME: C++ [temp.local]p8
DeclContext *Entity = nullptr;
if (S->getEntity()) {
DeclContext *Entity = S->getLookupEntity();
if (Entity) {
// Look into this scope's declaration context, along with any of its
// parent lookup contexts (e.g., enclosing classes), up to the point
// where we hit the context stored in the next outer scope.
Entity = S->getEntity();
DeclContext *OuterCtx = findOuterContext(S).first; // FIXME
DeclContext *OuterCtx = findOuterContext(S);
for (DeclContext *Ctx = Entity; Ctx && !Ctx->Equals(OuterCtx);
Ctx = Ctx->getLookupParent()) {

View File

@ -51,8 +51,7 @@ unsigned Sema::getTemplateDepth(Scope *S) const {
// Each template parameter scope represents one level of template parameter
// depth.
for (Scope *TempParamScope = S->getTemplateParamParent();
TempParamScope && !Depth;
for (Scope *TempParamScope = S->getTemplateParamParent(); TempParamScope;
TempParamScope = TempParamScope->getParent()->getTemplateParamParent()) {
++Depth;
}

View File

@ -690,7 +690,7 @@ namespace dr457 { // dr457: yes
};
}
namespace dr458 { // dr458: no
namespace dr458 { // dr458: 11
struct A {
int T;
int f();
@ -706,9 +706,9 @@ namespace dr458 { // dr458: no
int A::f() {
return T;
}
template<typename T>
template<typename T> // expected-note {{declared here}}
int A::g() {
return T; // FIXME: this is invalid, it finds the template parameter
return T; // expected-error {{'T' does not refer to a value}}
}
template<typename T>
@ -719,9 +719,9 @@ namespace dr458 { // dr458: no
int B<T>::g() {
return T;
}
template<typename U> template<typename T>
template<typename U> template<typename T> // expected-note {{declared here}}
int B<U>::h() {
return T; // FIXME: this is invalid, it finds the template parameter
return T; // expected-error {{'T' does not refer to a value}}
}
}

View File

@ -1,5 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// expected-no-diagnostics
namespace N {
enum { C };
@ -52,3 +51,63 @@ void N::Y::f(D) {
D d;
}
// Ensure we properly interleave the searches within classes and template parameter lists.
namespace SearchClassBetweenTemplateParameterLists {
int AA, BB; // none of the below lookups should ever consider these
template<typename T> struct A {
using AA = void;
template<typename U> struct B {
using BB = void;
void f(U);
void g(U);
void h(T);
void i(T);
template<typename V> void j(V);
template<typename V> void k(U);
};
};
// Search order for the below is:
// 1) template parameter scope of the function itself (if any)
// 2) class of which function is a member
// 3) template parameter scope of inner class
// 4) class of which class is a member
// 5) template parameter scope of outer class
// OK, 'AA' found in (3)
template<typename T> template<typename AA>
void A<T>::B<AA>::f(AA) {
AA aa;
}
// error, 'BB' found in (2)
template<typename T> template<typename BB>
void A<T>::B<BB>::g(BB) { // expected-error {{does not match}}
BB bb; // expected-error {{incomplete type}}
}
// error, 'AA' found in (4)
template<typename AA> template<typename U>
void A<AA>::B<U>::h(AA) { // expected-error {{does not match}}
AA aa; // expected-error {{incomplete type}}
}
// error, 'BB' found in (2)
template<typename BB> template<typename U>
void A<BB>::B<U>::i(BB) { // expected-error {{does not match}}
BB bb; // expected-error {{incomplete type}}
}
// OK, 'BB' found in (1)
template<typename T> template<typename U> template<typename BB>
void A<T>::B<U>::j(BB) {
BB bb;
}
// error, 'BB' found in (2)
template<typename T> template<typename BB> template<typename V>
void A<T>::B<BB>::k(V) { // expected-error {{does not match}}
BB bb; // expected-error {{incomplete type}}
}
}

View File

@ -601,16 +601,24 @@ namespace ConversionOperatorDoesNotHaveDeducedReturnType {
#if __cplusplus > 201402L
friend constexpr auto T::operator()(int) const;
friend constexpr T::operator ExpectedTypeT() const noexcept;
template<typename T>
friend constexpr void U::operator()(T&) const;
// FIXME: This should not match; the return type is specified as behaving
// "as if it were a decltype-specifier denoting the return type of
// [operator()]", which is not equivalent to this alias template.
template<typename T>
friend constexpr U::operator ExpectedTypeU<T>() const noexcept;
#else
friend auto T::operator()(int) const;
friend T::operator ExpectedTypeT() const;
#endif
// FIXME: The first of these should match. The second should not.
template<typename T>
friend void U::operator()(T&) const; // expected-error {{does not match}}
friend void U::operator()(T&) const;
// FIXME: This should not match, as above.
template<typename T>
friend U::operator ExpectedTypeU<T>() const; // expected-error {{does not match}}
friend U::operator ExpectedTypeU<T>() const;
#endif
private:
int n;

View File

@ -2789,7 +2789,7 @@ of class templates</td>
<td><a href="https://wg21.link/cwg458">458</a></td>
<td>C++11</td>
<td>Hiding of member template parameters by other members</td>
<td class="none" align="center">No</td>
<td class="unreleased" align="center">Clang 11</td>
</tr>
<tr class="open" id="459">
<td><a href="https://wg21.link/cwg459">459</a></td>
@ -12307,7 +12307,7 @@ and <I>POD class</I></td>
<td><a href="https://wg21.link/cwg2082">2082</a></td>
<td>CD4</td>
<td>Referring to parameters in unevaluated operands of default arguments</td>
<td class="none" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 11</td>
</tr>
<tr id="2083">
<td><a href="https://wg21.link/cwg2083">2083</a></td>
@ -13891,7 +13891,7 @@ and <I>POD class</I></td>
<td><a href="https://wg21.link/cwg2346">2346</a></td>
<td>DRWP</td>
<td>Local variables in default arguments</td>
<td class="none" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 11</td>
</tr>
<tr id="2347">
<td><a href="https://wg21.link/cwg2347">2347</a></td>