[Clang] Adjust concept definition locus (#103867)

Per [basic.scope], the locus of a concept is immediately after the
introduction of its name.

This let us provide better diagnostics for attempt to define recursive
concepts.

Note that recursive concepts are not supported per
https://eel.is/c++draft/basic#scope.pdecl-note-3, but there is no
normative wording for that restriction. This is a known defect
introduced by
[p1787r6](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1787r6.html).

Fixes #55875
This commit is contained in:
cor3ntin
2024-08-14 19:56:39 +02:00
committed by GitHub
parent c4ae8b1504
commit 0dedd6fe14
9 changed files with 119 additions and 35 deletions

View File

@@ -220,8 +220,10 @@ Bug Fixes to C++ Support
- Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667),
(#GH99877).
- Fixed a bug when diagnosing ambiguous explicit specializations of constrained member functions.
- Fixed an assertion failure when selecting a function from an overload set that includes a
- Fixed an assertion failure when selecting a function from an overload set that includes a
specialization of a conversion function template.
- Correctly diagnose attempts to use a concept name in its own definition;
A concept name is introduced to its scope sooner to match the C++ standard. (#GH55875)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -3146,19 +3146,24 @@ protected:
: TemplateDecl(Concept, DC, L, Name, Params),
ConstraintExpr(ConstraintExpr) {};
public:
static ConceptDecl *Create(ASTContext &C, DeclContext *DC,
SourceLocation L, DeclarationName Name,
static ConceptDecl *Create(ASTContext &C, DeclContext *DC, SourceLocation L,
DeclarationName Name,
TemplateParameterList *Params,
Expr *ConstraintExpr);
Expr *ConstraintExpr = nullptr);
static ConceptDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID);
Expr *getConstraintExpr() const {
return ConstraintExpr;
}
bool hasDefinition() const { return ConstraintExpr != nullptr; }
void setDefinition(Expr *E) { ConstraintExpr = E; }
SourceRange getSourceRange() const override LLVM_READONLY {
return SourceRange(getTemplateParameters()->getTemplateLoc(),
ConstraintExpr->getEndLoc());
ConstraintExpr ? ConstraintExpr->getEndLoc()
: SourceLocation());
}
bool isTypeConcept() const {

View File

@@ -3012,6 +3012,8 @@ def err_concept_no_parameters : Error<
"specialization of concepts is not allowed">;
def err_concept_extra_headers : Error<
"extraneous template parameter list in concept definition">;
def err_recursive_concept : Error<
"a concept definition cannot refer to itself">;
def err_concept_no_associated_constraints : Error<
"concept cannot have associated constraints">;
def err_non_constant_constraint_expression : Error<

View File

@@ -12033,14 +12033,17 @@ public:
void CheckDeductionGuideTemplate(FunctionTemplateDecl *TD);
Decl *ActOnConceptDefinition(Scope *S,
MultiTemplateParamsArg TemplateParameterLists,
const IdentifierInfo *Name,
SourceLocation NameLoc, Expr *ConstraintExpr,
const ParsedAttributesView &Attrs);
ConceptDecl *ActOnStartConceptDefinition(
Scope *S, MultiTemplateParamsArg TemplateParameterLists,
const IdentifierInfo *Name, SourceLocation NameLoc);
ConceptDecl *ActOnFinishConceptDefinition(Scope *S, ConceptDecl *C,
Expr *ConstraintExpr,
const ParsedAttributesView &Attrs);
void CheckConceptRedefinition(ConceptDecl *NewDecl, LookupResult &Previous,
bool &AddToScope);
bool CheckConceptUseInDefinition(ConceptDecl *Concept, SourceLocation Loc);
TypeResult ActOnDependentTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
const CXXScopeSpec &SS,

View File

@@ -320,6 +320,11 @@ Parser::ParseConceptDefinition(const ParsedTemplateInfo &TemplateInfo,
const IdentifierInfo *Id = Result.Identifier;
SourceLocation IdLoc = Result.getBeginLoc();
// [C++26][basic.scope.pdecl]/p13
// The locus of a concept-definition is immediately after its concept-name.
ConceptDecl *D = Actions.ActOnStartConceptDefinition(
getCurScope(), *TemplateInfo.TemplateParams, Id, IdLoc);
ParsedAttributes Attrs(AttrFactory);
MaybeParseAttributes(PAKM_GNU | PAKM_CXX11, Attrs);
@@ -339,9 +344,12 @@ Parser::ParseConceptDefinition(const ParsedTemplateInfo &TemplateInfo,
DeclEnd = Tok.getLocation();
ExpectAndConsumeSemi(diag::err_expected_semi_declaration);
Expr *ConstraintExpr = ConstraintExprResult.get();
return Actions.ActOnConceptDefinition(getCurScope(),
*TemplateInfo.TemplateParams, Id, IdLoc,
ConstraintExpr, Attrs);
if (!D)
return nullptr;
return Actions.ActOnFinishConceptDefinition(getCurScope(), D, ConstraintExpr,
Attrs);
}
/// ParseTemplateParameters - Parses a template-parameter-list enclosed in

View File

@@ -306,6 +306,10 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
}
if (auto *Concept = dyn_cast<ConceptDecl>(D);
Concept && CheckConceptUseInDefinition(Concept, Loc))
return true;
if (auto *MD = dyn_cast<CXXMethodDecl>(D)) {
// Lambdas are only default-constructible or assignable in C++2a onwards.
if (MD->getParent()->isLambda() &&

View File

@@ -1079,6 +1079,9 @@ bool Sema::CheckTypeConstraint(TemplateIdAnnotation *TypeConstr) {
return true;
}
if (CheckConceptUseInDefinition(CD, TypeConstr->TemplateNameLoc))
return true;
bool WereArgsSpecified = TypeConstr->LAngleLoc.isValid();
if (!WereArgsSpecified &&
@@ -8447,10 +8450,9 @@ Decl *Sema::ActOnTemplateDeclarator(Scope *S,
return NewDecl;
}
Decl *Sema::ActOnConceptDefinition(
ConceptDecl *Sema::ActOnStartConceptDefinition(
Scope *S, MultiTemplateParamsArg TemplateParameterLists,
const IdentifierInfo *Name, SourceLocation NameLoc, Expr *ConstraintExpr,
const ParsedAttributesView &Attrs) {
const IdentifierInfo *Name, SourceLocation NameLoc) {
DeclContext *DC = CurContext;
if (!DC->getRedeclContext()->isFileContext()) {
@@ -8486,11 +8488,8 @@ Decl *Sema::ActOnConceptDefinition(
}
}
if (DiagnoseUnexpandedParameterPack(ConstraintExpr))
return nullptr;
ConceptDecl *NewDecl =
ConceptDecl::Create(Context, DC, NameLoc, Name, Params, ConstraintExpr);
ConceptDecl::Create(Context, DC, NameLoc, Name, Params);
if (NewDecl->hasAssociatedConstraints()) {
// C++2a [temp.concept]p4:
@@ -8499,25 +8498,65 @@ Decl *Sema::ActOnConceptDefinition(
NewDecl->setInvalidDecl();
}
// Check for conflicting previous declaration.
DeclarationNameInfo NameInfo(NewDecl->getDeclName(), NameLoc);
DeclarationNameInfo NameInfo(NewDecl->getDeclName(), NewDecl->getBeginLoc());
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
forRedeclarationInCurContext());
LookupName(Previous, S);
FilterLookupForScope(Previous, DC, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/false);
bool AddToScope = true;
CheckConceptRedefinition(NewDecl, Previous, AddToScope);
FilterLookupForScope(Previous, CurContext, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/ false);
ActOnDocumentableDecl(NewDecl);
if (AddToScope)
PushOnScopeChains(NewDecl, S);
ProcessDeclAttributeList(S, NewDecl, Attrs);
// We cannot properly handle redeclarations until we parse the constraint
// expression, so only inject the name if we are sure we are not redeclaring a
// symbol
if (Previous.empty())
PushOnScopeChains(NewDecl, S, true);
return NewDecl;
}
static bool RemoveLookupResult(LookupResult &R, NamedDecl *C) {
bool Found = false;
LookupResult::Filter F = R.makeFilter();
while (F.hasNext()) {
NamedDecl *D = F.next();
if (D == C) {
F.erase();
Found = true;
break;
}
}
F.done();
return Found;
}
ConceptDecl *
Sema::ActOnFinishConceptDefinition(Scope *S, ConceptDecl *C,
Expr *ConstraintExpr,
const ParsedAttributesView &Attrs) {
assert(!C->hasDefinition() && "Concept already defined");
if (DiagnoseUnexpandedParameterPack(ConstraintExpr))
return nullptr;
C->setDefinition(ConstraintExpr);
ProcessDeclAttributeList(S, C, Attrs);
// Check for conflicting previous declaration.
DeclarationNameInfo NameInfo(C->getDeclName(), C->getBeginLoc());
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
forRedeclarationInCurContext());
LookupName(Previous, S);
FilterLookupForScope(Previous, CurContext, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/ false);
bool WasAlreadyAdded = RemoveLookupResult(Previous, C);
bool AddToScope = true;
CheckConceptRedefinition(C, Previous, AddToScope);
ActOnDocumentableDecl(C);
if (!WasAlreadyAdded && AddToScope)
PushOnScopeChains(C, S);
return C;
}
void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
LookupResult &Previous, bool &AddToScope) {
AddToScope = true;
@@ -8560,6 +8599,16 @@ void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
Context.setPrimaryMergedDecl(NewDecl, OldConcept->getCanonicalDecl());
}
bool Sema::CheckConceptUseInDefinition(ConceptDecl *Concept,
SourceLocation Loc) {
if (!Concept->isInvalidDecl() && !Concept->hasDefinition()) {
Diag(Loc, diag::err_recursive_concept) << Concept;
Diag(Concept->getLocation(), diag::note_declared_at);
return true;
}
return false;
}
/// \brief Strips various properties off an implicit instantiation
/// that has just been explicitly specialized.
static void StripImplicitInstantiation(NamedDecl *D, bool MinGW) {

View File

@@ -201,7 +201,9 @@ namespace cwg2565 { // cwg2565: 16 open 2023-06-07
template<typename T>
concept ErrorRequires = requires (ErrorRequires auto x) {
// since-cxx20-error@-1 {{unknown type name 'ErrorRequires'}}
// since-cxx20-error@-1 {{a concept definition cannot refer to itself}} \
// since-cxx20-error@-1 {{'auto' not allowed in requires expression parameter}} \
// since-cxx20-note@-1 {{declared here}}
x;
};
static_assert(ErrorRequires<int>);
@@ -209,9 +211,11 @@ namespace cwg2565 { // cwg2565: 16 open 2023-06-07
// since-cxx20-note@-2 {{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}}
template<typename T>
concept NestedErrorInRequires = requires (T x) {
concept NestedErrorInRequires = requires (T x) { //
// since-cxx20-note@-1 {{declared here}}
requires requires (NestedErrorInRequires auto y) {
// since-cxx20-error@-1 {{unknown type name 'NestedErrorInRequires'}}
// since-cxx20-error@-1 {{a concept definition cannot refer to itself}} \
// since-cxx20-error@-1 {{'auto' not allowed in requires expression parameter}}
y;
};
};

View File

@@ -1006,7 +1006,14 @@ template<class>
concept Irrelevant = false;
template <typename T>
concept ErrorRequires = requires(ErrorRequires auto x) { x; }; // expected-error {{unknown type name 'ErrorRequires'}}
concept ErrorRequires = requires(ErrorRequires auto x) { x; };
// expected-error@-1 {{a concept definition cannot refer to itself}} \
// expected-error@-1 {{'auto' not allowed in requires expression parameter}} \
// expected-note@-1 {{declared here}}
template<typename T> concept C1 = C1<T> && []<C1>(C1 auto) -> C1 auto {};
//expected-error@-1 4{{a concept definition cannot refer to itself}} \
//expected-note@-1 4{{declared here}}
template<class T> void aaa(T t) // expected-note {{candidate template ignored: constraints not satisfied}}
requires (False<T> || False<T>) || False<T> {} // expected-note 3 {{'int' does not satisfy 'False'}}