Skip to content

Commit

Permalink
[Clang] Adjust concept definition locus (#103867)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cor3ntin authored Aug 14, 2024
1 parent c4ae8b1 commit 0dedd6f
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 34 deletions.
4 changes: 3 additions & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
13 changes: 9 additions & 4 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -3146,19 +3146,24 @@ class ConceptDecl : public TemplateDecl, public Mergeable<ConceptDecl> {
: 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 {
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
13 changes: 8 additions & 5 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -12033,14 +12033,17 @@ class Sema final : public SemaBase {

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,
Expand Down
14 changes: 11 additions & 3 deletions clang/lib/Parse/ParseTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() &&
Expand Down
83 changes: 66 additions & 17 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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:
Expand All @@ -8499,23 +8498,63 @@ Decl *Sema::ActOnConceptDefinition(
NewDecl->setInvalidDecl();
}

DeclarationNameInfo NameInfo(NewDecl->getDeclName(), NewDecl->getBeginLoc());
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
forRedeclarationInCurContext());
LookupName(Previous, S);
FilterLookupForScope(Previous, CurContext, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/ false);

// 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(NewDecl->getDeclName(), NameLoc);
DeclarationNameInfo NameInfo(C->getDeclName(), C->getBeginLoc());
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
forRedeclarationInCurContext());
LookupName(Previous, S);
FilterLookupForScope(Previous, DC, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/false);
FilterLookupForScope(Previous, CurContext, S, /*ConsiderLinkage=*/false,
/*AllowInlineNamespace*/ false);
bool WasAlreadyAdded = RemoveLookupResult(Previous, C);
bool AddToScope = true;
CheckConceptRedefinition(NewDecl, Previous, AddToScope);
CheckConceptRedefinition(C, Previous, AddToScope);

ActOnDocumentableDecl(NewDecl);
if (AddToScope)
PushOnScopeChains(NewDecl, S);

ProcessDeclAttributeList(S, NewDecl, Attrs);
ActOnDocumentableDecl(C);
if (!WasAlreadyAdded && AddToScope)
PushOnScopeChains(C, S);

return NewDecl;
return C;
}

void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 7 additions & 3 deletions clang/test/CXX/drs/cwg25xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,21 @@ 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>);
// since-cxx20-error@-1 {{static assertion failed}}
// 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;
};
};
Expand Down
9 changes: 8 additions & 1 deletion clang/test/SemaTemplate/concepts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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'}}
Expand Down

0 comments on commit 0dedd6f

Please sign in to comment.