[c++20] Implement P0846R0: allow (ADL-only) calls to template-ids whose
template name is not visible to unqualified lookup.
In order to support this without a severe degradation in our ability to
diagnose typos in template names, this change significantly restructures
the way we handle template-id-shaped syntax for which lookup of the
template name finds nothing.
Instead of eagerly diagnosing an undeclared template name, we now form a
placeholder template-name representing a name that is known to not find
any templates. When the parser sees such a name, it attempts to
disambiguate whether we have a less-than comparison or a template-id.
Any diagnostics or typo-correction for the name are delayed until its
point of use.
The upshot should be a small improvement of our diagostic quality
overall: we now take more syntactic context into account when trying to
resolve an undeclared identifier on the left hand side of a '<'. In
fact, this works well enough that the backwards-compatible portion (for
an undeclared identifier rather than a lookup that finds functions but
no function templates) is enabled in all language modes.
llvm-svn: 360308
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index 93912da..c473856 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -888,7 +888,7 @@
bool Sema::ActOnCXXNestedNameSpecifier(Scope *S,
CXXScopeSpec &SS,
SourceLocation TemplateKWLoc,
- TemplateTy Template,
+ TemplateTy OpaqueTemplate,
SourceLocation TemplateNameLoc,
SourceLocation LAngleLoc,
ASTTemplateArgsPtr TemplateArgsIn,
@@ -898,11 +898,13 @@
if (SS.isInvalid())
return true;
+ TemplateName Template = OpaqueTemplate.get();
+
// Translate the parser's template argument list in our AST format.
TemplateArgumentListInfo TemplateArgs(LAngleLoc, RAngleLoc);
translateTemplateArguments(TemplateArgsIn, TemplateArgs);
- DependentTemplateName *DTN = Template.get().getAsDependentTemplateName();
+ DependentTemplateName *DTN = Template.getAsDependentTemplateName();
if (DTN && DTN->isIdentifier()) {
// Handle a dependent template specialization for which we cannot resolve
// the template name.
@@ -930,23 +932,28 @@
return false;
}
- TemplateDecl *TD = Template.get().getAsTemplateDecl();
- if (Template.get().getAsOverloadedTemplate() || DTN ||
+ // If we assumed an undeclared identifier was a template name, try to
+ // typo-correct it now.
+ if (Template.getAsAssumedTemplateName() &&
+ resolveAssumedTemplateNameAsType(S, Template, TemplateNameLoc))
+ return true;
+
+ TemplateDecl *TD = Template.getAsTemplateDecl();
+ if (Template.getAsOverloadedTemplate() || DTN ||
isa<FunctionTemplateDecl>(TD) || isa<VarTemplateDecl>(TD)) {
SourceRange R(TemplateNameLoc, RAngleLoc);
if (SS.getRange().isValid())
R.setBegin(SS.getRange().getBegin());
Diag(CCLoc, diag::err_non_type_template_in_nested_name_specifier)
- << (TD && isa<VarTemplateDecl>(TD)) << Template.get() << R;
- NoteAllFoundTemplates(Template.get());
+ << (TD && isa<VarTemplateDecl>(TD)) << Template << R;
+ NoteAllFoundTemplates(Template);
return true;
}
// We were able to resolve the template name to an actual template.
// Build an appropriate nested-name-specifier.
- QualType T =
- CheckTemplateIdType(Template.get(), TemplateNameLoc, TemplateArgs);
+ QualType T = CheckTemplateIdType(Template, TemplateNameLoc, TemplateArgs);
if (T.isNull())
return true;
@@ -954,7 +961,7 @@
// nested name specifiers.
if (!T->isDependentType() && !T->getAs<TagType>()) {
Diag(TemplateNameLoc, diag::err_nested_name_spec_non_tag) << T;
- NoteAllFoundTemplates(Template.get());
+ NoteAllFoundTemplates(Template);
return true;
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 3ac5a37..ea6675f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -917,6 +917,16 @@
}
}
+ if (getLangOpts().CPlusPlus2a && !SS.isSet() && NextToken.is(tok::less)) {
+ // In C++20 onwards, this could be an ADL-only call to a function
+ // template, and we're required to assume that this is a template name.
+ //
+ // FIXME: Find a way to still do typo correction in this case.
+ TemplateName Template =
+ Context.getAssumedTemplateName(NameInfo.getName());
+ return NameClassification::UndeclaredTemplate(Template);
+ }
+
// In C, we first see whether there is a tag type by the same name, in
// which case it's likely that the user just forgot to write "enum",
// "struct", or "union".
@@ -1045,52 +1055,62 @@
if (getLangOpts().CPlusPlus && NextToken.is(tok::less) &&
(IsFilteredTemplateName ||
- hasAnyAcceptableTemplateNames(Result, /*AllowFunctionTemplates=*/true,
- /*AllowDependent=*/false))) {
+ hasAnyAcceptableTemplateNames(
+ Result, /*AllowFunctionTemplates=*/true,
+ /*AllowDependent=*/false,
+ /*AllowNonTemplateFunctions*/ !SS.isSet() &&
+ getLangOpts().CPlusPlus2a))) {
// C++ [temp.names]p3:
// After name lookup (3.4) finds that a name is a template-name or that
// an operator-function-id or a literal- operator-id refers to a set of
// overloaded functions any member of which is a function template if
// this is followed by a <, the < is always taken as the delimiter of a
// template-argument-list and never as the less-than operator.
+ // C++2a [temp.names]p2:
+ // A name is also considered to refer to a template if it is an
+ // unqualified-id followed by a < and name lookup finds either one
+ // or more functions or finds nothing.
if (!IsFilteredTemplateName)
FilterAcceptableTemplateNames(Result);
- if (!Result.empty()) {
- bool IsFunctionTemplate;
- bool IsVarTemplate;
- TemplateName Template;
- if (Result.end() - Result.begin() > 1) {
- IsFunctionTemplate = true;
- Template = Context.getOverloadedTemplateName(Result.begin(),
- Result.end());
- } else {
- auto *TD = cast<TemplateDecl>(getAsTemplateNameDecl(
- *Result.begin(), /*AllowFunctionTemplates=*/true,
- /*AllowDependent=*/false));
- IsFunctionTemplate = isa<FunctionTemplateDecl>(TD);
- IsVarTemplate = isa<VarTemplateDecl>(TD);
+ bool IsFunctionTemplate;
+ bool IsVarTemplate;
+ TemplateName Template;
+ if (Result.end() - Result.begin() > 1) {
+ IsFunctionTemplate = true;
+ Template = Context.getOverloadedTemplateName(Result.begin(),
+ Result.end());
+ } else if (!Result.empty()) {
+ auto *TD = cast<TemplateDecl>(getAsTemplateNameDecl(
+ *Result.begin(), /*AllowFunctionTemplates=*/true,
+ /*AllowDependent=*/false));
+ IsFunctionTemplate = isa<FunctionTemplateDecl>(TD);
+ IsVarTemplate = isa<VarTemplateDecl>(TD);
- if (SS.isSet() && !SS.isInvalid())
- Template =
- Context.getQualifiedTemplateName(SS.getScopeRep(),
- /*TemplateKeyword=*/false, TD);
- else
- Template = TemplateName(TD);
- }
-
- if (IsFunctionTemplate) {
- // Function templates always go through overload resolution, at which
- // point we'll perform the various checks (e.g., accessibility) we need
- // to based on which function we selected.
- Result.suppressDiagnostics();
-
- return NameClassification::FunctionTemplate(Template);
- }
-
- return IsVarTemplate ? NameClassification::VarTemplate(Template)
- : NameClassification::TypeTemplate(Template);
+ if (SS.isSet() && !SS.isInvalid())
+ Template =
+ Context.getQualifiedTemplateName(SS.getScopeRep(),
+ /*TemplateKeyword=*/false, TD);
+ else
+ Template = TemplateName(TD);
+ } else {
+ // All results were non-template functions. This is a function template
+ // name.
+ IsFunctionTemplate = true;
+ Template = Context.getAssumedTemplateName(NameInfo.getName());
}
+
+ if (IsFunctionTemplate) {
+ // Function templates always go through overload resolution, at which
+ // point we'll perform the various checks (e.g., accessibility) we need
+ // to based on which function we selected.
+ Result.suppressDiagnostics();
+
+ return NameClassification::FunctionTemplate(Template);
+ }
+
+ return IsVarTemplate ? NameClassification::VarTemplate(Template)
+ : NameClassification::TypeTemplate(Template);
}
NamedDecl *FirstDecl = (*Result.begin())->getUnderlyingDecl();
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a47c404..04192e3 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -3881,6 +3881,8 @@
if (TemplateTypeTy) {
BaseType = GetTypeFromParser(TemplateTypeTy, &TInfo);
+ if (BaseType.isNull())
+ return true;
} else if (DS.getTypeSpecType() == TST_decltype) {
BaseType = BuildDecltypeType(DS.getRepAsExpr(), DS.getTypeSpecTypeLoc());
} else if (DS.getTypeSpecType() == TST_decltype_auto) {
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index f365d82..31d00dc 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -2065,11 +2065,12 @@
// is in the wrong place to recover. Suggest the typo
// correction, but don't make it a fix-it since we're not going
// to recover well anyway.
- AcceptableWithoutRecovery =
- isa<TypeDecl>(UnderlyingND) || isa<ObjCInterfaceDecl>(UnderlyingND);
+ AcceptableWithoutRecovery = isa<TypeDecl>(UnderlyingND) ||
+ getAsTypeTemplateDecl(UnderlyingND) ||
+ isa<ObjCInterfaceDecl>(UnderlyingND);
} else {
- // FIXME: We found a keyword or a type. Suggest it, but don't provide a
- // fix-it because we aren't able to recover.
+ // FIXME: We found a keyword. Suggest it, but don't provide a fix-it
+ // because we aren't able to recover.
AcceptableWithoutRecovery = true;
}
@@ -2221,8 +2222,10 @@
// this becomes a performance hit, we can work harder to preserve those
// results until we get here but it's likely not worth it.
bool MemberOfUnknownSpecialization;
+ AssumedTemplateKind AssumedTemplate;
if (LookupTemplateName(R, S, SS, QualType(), /*EnteringContext=*/false,
- MemberOfUnknownSpecialization, TemplateKWLoc))
+ MemberOfUnknownSpecialization, TemplateKWLoc,
+ &AssumedTemplate))
return ExprError();
if (MemberOfUnknownSpecialization ||
@@ -5518,7 +5521,24 @@
ExprResult Sema::ActOnCallExpr(Scope *Scope, Expr *Fn, SourceLocation LParenLoc,
MultiExprArg ArgExprs, SourceLocation RParenLoc,
Expr *ExecConfig) {
- return BuildCallExpr(Scope, Fn, LParenLoc, ArgExprs, RParenLoc, ExecConfig);
+ ExprResult Call =
+ BuildCallExpr(Scope, Fn, LParenLoc, ArgExprs, RParenLoc, ExecConfig);
+ if (Call.isInvalid())
+ return Call;
+
+ // Diagnose uses of the C++20 "ADL-only template-id call" feature in earlier
+ // language modes.
+ if (auto *ULE = dyn_cast<UnresolvedLookupExpr>(Fn)) {
+ if (ULE->hasExplicitTemplateArgs() &&
+ ULE->decls_begin() == ULE->decls_end()) {
+ Diag(Fn->getExprLoc(), getLangOpts().CPlusPlus2a
+ ? diag::warn_cxx17_compat_adl_only_template_id
+ : diag::ext_adl_only_template_id)
+ << ULE->getName();
+ }
+ }
+
+ return Call;
}
/// BuildCallExpr - Handle a call to Fn with the specified array of arguments.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 881e0f6..6af59d5 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7074,7 +7074,8 @@
TemplateIdAnnotation *TemplateId = SecondTypeName.TemplateId;
ASTTemplateArgsPtr TemplateArgsPtr(TemplateId->getTemplateArgs(),
TemplateId->NumArgs);
- TypeResult T = ActOnTemplateIdType(TemplateId->SS,
+ TypeResult T = ActOnTemplateIdType(S,
+ TemplateId->SS,
TemplateId->TemplateKWLoc,
TemplateId->Template,
TemplateId->Name,
@@ -7126,7 +7127,8 @@
TemplateIdAnnotation *TemplateId = FirstTypeName.TemplateId;
ASTTemplateArgsPtr TemplateArgsPtr(TemplateId->getTemplateArgs(),
TemplateId->NumArgs);
- TypeResult T = ActOnTemplateIdType(TemplateId->SS,
+ TypeResult T = ActOnTemplateIdType(S,
+ TemplateId->SS,
TemplateId->TemplateKWLoc,
TemplateId->Template,
TemplateId->Name,
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 3fbd635..19ac2bf 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -12017,7 +12017,8 @@
// We don't perform ADL for implicit declarations of builtins.
// Verify that this was correctly set up.
FunctionDecl *F;
- if (ULE->decls_begin() + 1 == ULE->decls_end() &&
+ if (ULE->decls_begin() != ULE->decls_end() &&
+ ULE->decls_begin() + 1 == ULE->decls_end() &&
(F = dyn_cast<FunctionDecl>(*ULE->decls_begin())) &&
F->getBuiltinID() && F->isImplicit())
llvm_unreachable("performing ADL for builtin");
@@ -12201,10 +12202,9 @@
OverloadingResult OverloadResult =
CandidateSet.BestViableFunction(*this, Fn->getBeginLoc(), Best);
- return FinishOverloadedCallExpr(*this, S, Fn, ULE, LParenLoc, Args,
- RParenLoc, ExecConfig, &CandidateSet,
- &Best, OverloadResult,
- AllowTypoCorrection);
+ return FinishOverloadedCallExpr(*this, S, Fn, ULE, LParenLoc, Args, RParenLoc,
+ ExecConfig, &CandidateSet, &Best,
+ OverloadResult, AllowTypoCorrection);
}
static bool IsOverloaded(const UnresolvedSetImpl &Functions) {
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 58ad439..c58c446 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -130,10 +130,15 @@
bool Sema::hasAnyAcceptableTemplateNames(LookupResult &R,
bool AllowFunctionTemplates,
- bool AllowDependent) {
- for (LookupResult::iterator I = R.begin(), IEnd = R.end(); I != IEnd; ++I)
+ bool AllowDependent,
+ bool AllowNonTemplateFunctions) {
+ for (LookupResult::iterator I = R.begin(), IEnd = R.end(); I != IEnd; ++I) {
if (getAsTemplateNameDecl(*I, AllowFunctionTemplates, AllowDependent))
return true;
+ if (AllowNonTemplateFunctions &&
+ isa<FunctionDecl>((*I)->getUnderlyingDecl()))
+ return true;
+ }
return false;
}
@@ -171,11 +176,25 @@
QualType ObjectType = ObjectTypePtr.get();
+ AssumedTemplateKind AssumedTemplate;
LookupResult R(*this, TName, Name.getBeginLoc(), LookupOrdinaryName);
if (LookupTemplateName(R, S, SS, ObjectType, EnteringContext,
- MemberOfUnknownSpecialization))
+ MemberOfUnknownSpecialization, SourceLocation(),
+ &AssumedTemplate))
return TNK_Non_template;
- if (R.empty()) return TNK_Non_template;
+
+ if (AssumedTemplate != AssumedTemplateKind::None) {
+ TemplateResult = TemplateTy::make(Context.getAssumedTemplateName(TName));
+ // Let the parser know whether we found nothing or found functions; if we
+ // found nothing, we want to more carefully check whether this is actually
+ // a function template name versus some other kind of undeclared identifier.
+ return AssumedTemplate == AssumedTemplateKind::FoundNothing
+ ? TNK_Undeclared_template
+ : TNK_Function_template;
+ }
+
+ if (R.empty())
+ return TNK_Non_template;
NamedDecl *D = nullptr;
if (R.isAmbiguous()) {
@@ -325,7 +344,11 @@
QualType ObjectType,
bool EnteringContext,
bool &MemberOfUnknownSpecialization,
- SourceLocation TemplateKWLoc) {
+ SourceLocation TemplateKWLoc,
+ AssumedTemplateKind *ATK) {
+ if (ATK)
+ *ATK = AssumedTemplateKind::None;
+
Found.setTemplateNameLookup(true);
// Determine where to perform name lookup
@@ -405,6 +428,32 @@
if (Found.isAmbiguous())
return false;
+ if (ATK && !SS.isSet() && ObjectType.isNull() && TemplateKWLoc.isInvalid()) {
+ // C++2a [temp.names]p2:
+ // A name is also considered to refer to a template if it is an
+ // unqualified-id followed by a < and name lookup finds either one or more
+ // functions or finds nothing.
+ //
+ // To keep our behavior consistent, we apply the "finds nothing" part in
+ // all language modes, and diagnose the empty lookup in ActOnCallExpr if we
+ // successfully form a call to an undeclared template-id.
+ bool AllFunctions =
+ getLangOpts().CPlusPlus2a &&
+ std::all_of(Found.begin(), Found.end(), [](NamedDecl *ND) {
+ return isa<FunctionDecl>(ND->getUnderlyingDecl());
+ });
+ if (AllFunctions || (Found.empty() && !IsDependent)) {
+ // If lookup found any functions, or if this is a name that can only be
+ // used for a function, then strongly assume this is a function
+ // template-id.
+ *ATK = (Found.empty() && Found.getLookupName().isIdentifier())
+ ? AssumedTemplateKind::FoundNothing
+ : AssumedTemplateKind::FoundFunctions;
+ Found.clear();
+ return false;
+ }
+ }
+
if (Found.empty() && !IsDependent) {
// If we did not find any names, attempt to correct any typos.
DeclarationName Name = Found.getLookupName();
@@ -418,13 +467,13 @@
if (TypoCorrection Corrected =
CorrectTypo(Found.getLookupNameInfo(), Found.getLookupKind(), S,
&SS, FilterCCC, CTK_ErrorRecovery, LookupCtx)) {
- Found.setLookupName(Corrected.getCorrection());
if (auto *ND = Corrected.getFoundDecl())
Found.addDecl(ND);
FilterAcceptableTemplateNames(Found);
if (Found.isAmbiguous()) {
Found.clear();
} else if (!Found.empty()) {
+ Found.setLookupName(Corrected.getCorrection());
if (LookupCtx) {
std::string CorrectedStr(Corrected.getAsString(getLangOpts()));
bool DroppedSpecifier = Corrected.WillReplaceSpecifier() &&
@@ -436,8 +485,6 @@
diagnoseTypo(Corrected, PDiag(diag::err_no_template_suggest) << Name);
}
}
- } else {
- Found.setLookupName(Name);
}
}
@@ -3348,14 +3395,65 @@
return Context.getTemplateSpecializationType(Name, TemplateArgs, CanonType);
}
-TypeResult
-Sema::ActOnTemplateIdType(CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
- TemplateTy TemplateD, IdentifierInfo *TemplateII,
- SourceLocation TemplateIILoc,
- SourceLocation LAngleLoc,
- ASTTemplateArgsPtr TemplateArgsIn,
- SourceLocation RAngleLoc,
- bool IsCtorOrDtorName, bool IsClassName) {
+void Sema::ActOnUndeclaredTypeTemplateName(Scope *S, TemplateTy &ParsedName,
+ TemplateNameKind &TNK,
+ SourceLocation NameLoc,
+ IdentifierInfo *&II) {
+ assert(TNK == TNK_Undeclared_template && "not an undeclared template name");
+
+ TemplateName Name = ParsedName.get();
+ auto *ATN = Name.getAsAssumedTemplateName();
+ assert(ATN && "not an assumed template name");
+ II = ATN->getDeclName().getAsIdentifierInfo();
+
+ if (!resolveAssumedTemplateNameAsType(S, Name, NameLoc, /*Diagnose*/false)) {
+ // Resolved to a type template name.
+ ParsedName = TemplateTy::make(Name);
+ TNK = TNK_Type_template;
+ }
+}
+
+bool Sema::resolveAssumedTemplateNameAsType(Scope *S, TemplateName &Name,
+ SourceLocation NameLoc,
+ bool Diagnose) {
+ // We assumed this undeclared identifier to be an (ADL-only) function
+ // template name, but it was used in a context where a type was required.
+ // Try to typo-correct it now.
+ AssumedTemplateStorage *ATN = Name.getAsAssumedTemplateName();
+ assert(ATN && "not an assumed template name");
+
+ LookupResult R(*this, ATN->getDeclName(), NameLoc, LookupOrdinaryName);
+ struct CandidateCallback : CorrectionCandidateCallback {
+ bool ValidateCandidate(const TypoCorrection &TC) override {
+ return TC.getCorrectionDecl() &&
+ getAsTypeTemplateDecl(TC.getCorrectionDecl());
+ }
+ std::unique_ptr<CorrectionCandidateCallback> clone() override {
+ return llvm::make_unique<CandidateCallback>(*this);
+ }
+ } FilterCCC;
+
+ TypoCorrection Corrected =
+ CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), S, nullptr,
+ FilterCCC, CTK_ErrorRecovery);
+ if (Corrected && Corrected.getFoundDecl()) {
+ diagnoseTypo(Corrected, PDiag(diag::err_no_template_suggest)
+ << ATN->getDeclName());
+ Name = TemplateName(Corrected.getCorrectionDeclAs<TemplateDecl>());
+ return false;
+ }
+
+ if (Diagnose)
+ Diag(R.getNameLoc(), diag::err_no_template) << R.getLookupName();
+ return true;
+}
+
+TypeResult Sema::ActOnTemplateIdType(
+ Scope *S, CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
+ TemplateTy TemplateD, IdentifierInfo *TemplateII,
+ SourceLocation TemplateIILoc, SourceLocation LAngleLoc,
+ ASTTemplateArgsPtr TemplateArgsIn, SourceLocation RAngleLoc,
+ bool IsCtorOrDtorName, bool IsClassName) {
if (SS.isInvalid())
return true;
@@ -3396,6 +3494,9 @@
}
TemplateName Template = TemplateD.get();
+ if (Template.getAsAssumedTemplateName() &&
+ resolveAssumedTemplateNameAsType(S, Template, TemplateIILoc))
+ return true;
// Translate the parser's template argument list in our AST format.
TemplateArgumentListInfo TemplateArgs(LAngleLoc, RAngleLoc);
@@ -4141,7 +4242,6 @@
// vs template<class T, class U> void f(U);
// These should be filtered out by our callers.
- assert(!R.empty() && "empty lookup results when building templateid");
assert(!R.isAmbiguous() && "ambiguous lookup when building templateid");
// Non-function templates require a template argument list.