Skip to content

"friends can only be classes or functions" should also be diagnosed when trying to befriend a concept #45182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Quuxplusone opened this issue May 8, 2020 · 3 comments · Fixed by #105121
Assignees
Labels
bugzilla Issues migrated from bugzilla c++20 clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer concepts C++20 concepts confirmed Verified by a second party

Comments

@Quuxplusone
Copy link
Contributor

Bugzilla Link 45837
Version trunk
OS All
CC @mizvekov,@zygoloid

Extended Description

https://godbolt.org/z/jntYnj

Given this code:

struct S {
template
friend concept fooable;
};

Clang produces this harsh syntax error:

error: expected member name or ';' after declaration specifiers
friend concept fooable;
^

If you try to befriend any other kind of entity -- for example, a variable or variable template -- Clang gives a much friendlier error message, indicating that it understands what you were trying to do:

error: friends can only be classes or functions
friend bool fooable_v;
^

People are already trying to befriend concepts in the field, so it would be nice if Clang gave that same friendly error message (diag::err_unexpected_friend) for the concept case, as well.

@llvmbot llvmbot transferred this issue from llvm/llvm-bugzilla-archive Dec 10, 2021
@Quuxplusone Quuxplusone added the concepts C++20 concepts label Jan 16, 2022
@llvmbot llvmbot added the confirmed Verified by a second party label Jan 26, 2022
@xgupta xgupta self-assigned this Jun 16, 2022
@xgupta xgupta added the clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer label Jun 16, 2022
@usx95 usx95 moved this to In Progress in C++ 20 in Clang Aug 22, 2022
@usx95 usx95 moved this from In Progress to Triage Group 1 in C++ 20 in Clang Oct 14, 2022
@xgupta
Copy link
Contributor

xgupta commented Jan 21, 2023

I think we have to fix parser first to not emit this wrong error message - error: expected member name or ';' after declaration specifiers
here -

void Parser::ParseDirectDeclarator(Declarator &D) {
DeclaratorScopeObj DeclScopeObj(*this, D.getCXXScopeSpec());
if (getLangOpts().CPlusPlus && D.mayHaveIdentifier()) {
// This might be a C++17 structured binding.
if (Tok.is(tok::l_square) && !D.mayOmitIdentifier() &&
D.getCXXScopeSpec().isEmpty())
return ParseDecompositionDeclarator(D);
// Don't parse FOO:BAR as if it were a typo for FOO::BAR inside a class, in
// this context it is a bitfield. Also in range-based for statement colon
// may delimit for-range-declaration.
ColonProtectionRAIIObject X(
*this, D.getContext() == DeclaratorContext::Member ||
(D.getContext() == DeclaratorContext::ForInit &&
getLangOpts().CPlusPlus11));
// ParseDeclaratorInternal might already have parsed the scope.
if (D.getCXXScopeSpec().isEmpty()) {
bool EnteringContext = D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member;
ParseOptionalCXXScopeSpecifier(
D.getCXXScopeSpec(), /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false, EnteringContext);
}
if (D.getCXXScopeSpec().isValid()) {
if (Actions.ShouldEnterDeclaratorScope(getCurScope(),
D.getCXXScopeSpec()))
// Change the declaration context for name lookup, until this function
// is exited (and the declarator has been parsed).
DeclScopeObj.EnterDeclaratorScope();
else if (getObjCDeclContext()) {
// Ensure that we don't interpret the next token as an identifier when
// dealing with declarations in an Objective-C container.
D.SetIdentifier(nullptr, Tok.getLocation());
D.setInvalidType(true);
ConsumeToken();
goto PastIdentifier;
}
}
// C++0x [dcl.fct]p14:
// There is a syntactic ambiguity when an ellipsis occurs at the end of a
// parameter-declaration-clause without a preceding comma. In this case,
// the ellipsis is parsed as part of the abstract-declarator if the type
// of the parameter either names a template parameter pack that has not
// been expanded or contains auto; otherwise, it is parsed as part of the
// parameter-declaration-clause.
if (Tok.is(tok::ellipsis) && D.getCXXScopeSpec().isEmpty() &&
!((D.getContext() == DeclaratorContext::Prototype ||
D.getContext() == DeclaratorContext::LambdaExprParameter ||
D.getContext() == DeclaratorContext::BlockLiteral) &&
NextToken().is(tok::r_paren) && !D.hasGroupingParens() &&
!Actions.containsUnexpandedParameterPacks(D) &&
D.getDeclSpec().getTypeSpecType() != TST_auto)) {
SourceLocation EllipsisLoc = ConsumeToken();
if (isPtrOperatorToken(Tok.getKind(), getLangOpts(), D.getContext())) {
// The ellipsis was put in the wrong place. Recover, and explain to
// the user what they should have done.
ParseDeclarator(D);
if (EllipsisLoc.isValid())
DiagnoseMisplacedEllipsisInDeclarator(EllipsisLoc, D);
return;
} else
D.setEllipsisLoc(EllipsisLoc);
// The ellipsis can't be followed by a parenthesized declarator. We
// check for that in ParseParenDeclarator, after we have disambiguated
// the l_paren token.
}
if (Tok.isOneOf(tok::identifier, tok::kw_operator, tok::annot_template_id,
tok::tilde)) {
// We found something that indicates the start of an unqualified-id.
// Parse that unqualified-id.
bool AllowConstructorName;
bool AllowDeductionGuide;
if (D.getDeclSpec().hasTypeSpecifier()) {
AllowConstructorName = false;
AllowDeductionGuide = false;
} else if (D.getCXXScopeSpec().isSet()) {
AllowConstructorName = (D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member);
AllowDeductionGuide = false;
} else {
AllowConstructorName = (D.getContext() == DeclaratorContext::Member);
AllowDeductionGuide = (D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member);
}
bool HadScope = D.getCXXScopeSpec().isValid();
if (ParseUnqualifiedId(D.getCXXScopeSpec(),
/*ObjectType=*/nullptr,
/*ObjectHadErrors=*/false,
/*EnteringContext=*/true,
/*AllowDestructorName=*/true, AllowConstructorName,
AllowDeductionGuide, nullptr, D.getName()) ||
// Once we're past the identifier, if the scope was bad, mark the
// whole declarator bad.
D.getCXXScopeSpec().isInvalid()) {
D.SetIdentifier(nullptr, Tok.getLocation());
D.setInvalidType(true);
} else {
// ParseUnqualifiedId might have parsed a scope specifier during error
// recovery. If it did so, enter that scope.
if (!HadScope && D.getCXXScopeSpec().isValid() &&
Actions.ShouldEnterDeclaratorScope(getCurScope(),
D.getCXXScopeSpec()))
DeclScopeObj.EnterDeclaratorScope();
// Parsed the unqualified-id; update range information and move along.
if (D.getSourceRange().getBegin().isInvalid())
D.SetRangeBegin(D.getName().getSourceRange().getBegin());
D.SetRangeEnd(D.getName().getSourceRange().getEnd());
}
goto PastIdentifier;
}
if (D.getCXXScopeSpec().isNotEmpty()) {
// We have a scope specifier but no following unqualified-id.
Diag(PP.getLocForEndOfToken(D.getCXXScopeSpec().getEndLoc()),
diag::err_expected_unqualified_id)
<< /*C++*/1;
D.SetIdentifier(nullptr, Tok.getLocation());
goto PastIdentifier;
}
} else if (Tok.is(tok::identifier) && D.mayHaveIdentifier()) {
assert(!getLangOpts().CPlusPlus &&
"There's a C++-specific check for tok::identifier above");
assert(Tok.getIdentifierInfo() && "Not an identifier?");
D.SetIdentifier(Tok.getIdentifierInfo(), Tok.getLocation());
D.SetRangeEnd(Tok.getLocation());
ConsumeToken();
goto PastIdentifier;
} else if (Tok.is(tok::identifier) && !D.mayHaveIdentifier()) {
// We're not allowed an identifier here, but we got one. Try to figure out
// if the user was trying to attach a name to the type, or whether the name
// is some unrelated trailing syntax.
bool DiagnoseIdentifier = false;
if (D.hasGroupingParens())
// An identifier within parens is unlikely to be intended to be anything
// other than a name being "declared".
DiagnoseIdentifier = true;
else if (D.getContext() == DeclaratorContext::TemplateArg)
// T<int N> is an accidental identifier; T<int N indicates a missing '>'.
DiagnoseIdentifier =
NextToken().isOneOf(tok::comma, tok::greater, tok::greatergreater);
else if (D.getContext() == DeclaratorContext::AliasDecl ||
D.getContext() == DeclaratorContext::AliasTemplate)
// The most likely error is that the ';' was forgotten.
DiagnoseIdentifier = NextToken().isOneOf(tok::comma, tok::semi);
else if ((D.getContext() == DeclaratorContext::TrailingReturn ||
D.getContext() == DeclaratorContext::TrailingReturnVar) &&
!isCXX11VirtSpecifier(Tok))
DiagnoseIdentifier = NextToken().isOneOf(
tok::comma, tok::semi, tok::equal, tok::l_brace, tok::kw_try);
if (DiagnoseIdentifier) {
Diag(Tok.getLocation(), diag::err_unexpected_unqualified_id)
<< FixItHint::CreateRemoval(Tok.getLocation());
D.SetIdentifier(nullptr, Tok.getLocation());
ConsumeToken();
goto PastIdentifier;
}
}
if (Tok.is(tok::l_paren)) {
// If this might be an abstract-declarator followed by a direct-initializer,
// check whether this is a valid declarator chunk. If it can't be, assume
// that it's an initializer instead.
if (D.mayOmitIdentifier() && D.mayBeFollowedByCXXDirectInit()) {
RevertingTentativeParsingAction PA(*this);
if (TryParseDeclarator(true, D.mayHaveIdentifier(), true) ==
TPResult::False) {
D.SetIdentifier(nullptr, Tok.getLocation());
goto PastIdentifier;
}
}
// direct-declarator: '(' declarator ')'
// direct-declarator: '(' attributes declarator ')'
// Example: 'char (*X)' or 'int (*XX)(void)'
ParseParenDeclarator(D);
// If the declarator was parenthesized, we entered the declarator
// scope when parsing the parenthesized declarator, then exited
// the scope already. Re-enter the scope, if we need to.
if (D.getCXXScopeSpec().isSet()) {
// If there was an error parsing parenthesized declarator, declarator
// scope may have been entered before. Don't do it again.
if (!D.isInvalidType() &&
Actions.ShouldEnterDeclaratorScope(getCurScope(),
D.getCXXScopeSpec()))
// Change the declaration context for name lookup, until this function
// is exited (and the declarator has been parsed).
DeclScopeObj.EnterDeclaratorScope();
}
} else if (D.mayOmitIdentifier()) {
// This could be something simple like "int" (in which case the declarator
// portion is empty), if an abstract-declarator is allowed.
D.SetIdentifier(nullptr, Tok.getLocation());
// The grammar for abstract-pack-declarator does not allow grouping parens.
// FIXME: Revisit this once core issue 1488 is resolved.
if (D.hasEllipsis() && D.hasGroupingParens())
Diag(PP.getLocForEndOfToken(D.getEllipsisLoc()),
diag::ext_abstract_pack_declarator_parens);
} else {
if (Tok.getKind() == tok::annot_pragma_parser_crash)
LLVM_BUILTIN_TRAP;
if (Tok.is(tok::l_square))
return ParseMisplacedBracketDeclarator(D);
if (D.getContext() == DeclaratorContext::Member) {
// Objective-C++: Detect C++ keywords and try to prevent further errors by
// treating these keyword as valid member names.
if (getLangOpts().ObjC && getLangOpts().CPlusPlus &&
Tok.getIdentifierInfo() &&
Tok.getIdentifierInfo()->isCPlusPlusKeyword(getLangOpts())) {
Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
diag::err_expected_member_name_or_semi_objcxx_keyword)
<< Tok.getIdentifierInfo()
<< (D.getDeclSpec().isEmpty() ? SourceRange()
: D.getDeclSpec().getSourceRange());
D.SetIdentifier(Tok.getIdentifierInfo(), Tok.getLocation());
D.SetRangeEnd(Tok.getLocation());
ConsumeToken();
goto PastIdentifier;
}
Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
diag::err_expected_member_name_or_semi)
<< (D.getDeclSpec().isEmpty() ? SourceRange()
: D.getDeclSpec().getSourceRange());
} else {
if (Tok.getKind() == tok::TokenKind::kw_while) {
Diag(Tok, diag::err_while_loop_outside_of_a_function);
} else if (getLangOpts().CPlusPlus) {
if (Tok.isOneOf(tok::period, tok::arrow))
Diag(Tok, diag::err_invalid_operator_on_type) << Tok.is(tok::arrow);
else {
SourceLocation Loc = D.getCXXScopeSpec().getEndLoc();
if (Tok.isAtStartOfLine() && Loc.isValid())
Diag(PP.getLocForEndOfToken(Loc), diag::err_expected_unqualified_id)
<< getLangOpts().CPlusPlus;
else
Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
diag::err_expected_unqualified_id)
<< getLangOpts().CPlusPlus;
}
} else {
Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
diag::err_expected_either)
<< tok::identifier << tok::l_paren;
}
}
D.SetIdentifier(nullptr, Tok.getLocation());
D.setInvalidType(true);
}
PastIdentifier:
assert(D.isPastIdentifier() &&
"Haven't past the location of the identifier yet?");
// Don't parse attributes unless we have parsed an unparenthesized name.
if (D.hasName() && !D.getNumTypeObjects())
MaybeParseCXX11Attributes(D);
while (true) {
if (Tok.is(tok::l_paren)) {
bool IsFunctionDeclaration = D.isFunctionDeclaratorAFunctionDeclaration();
// Enter function-declaration scope, limiting any declarators to the
// function prototype scope, including parameter declarators.
ParseScope PrototypeScope(this,
Scope::FunctionPrototypeScope|Scope::DeclScope|
(IsFunctionDeclaration
? Scope::FunctionDeclarationScope : 0));
// The paren may be part of a C++ direct initializer, eg. "int x(1);".
// In such a case, check if we actually have a function declarator; if it
// is not, the declarator has been fully parsed.
bool IsAmbiguous = false;
if (getLangOpts().CPlusPlus && D.mayBeFollowedByCXXDirectInit()) {
// C++2a [temp.res]p5
// A qualified-id is assumed to name a type if
// - [...]
// - it is a decl-specifier of the decl-specifier-seq of a
// - [...]
// - parameter-declaration in a member-declaration [...]
// - parameter-declaration in a declarator of a function or function
// template declaration whose declarator-id is qualified [...]
auto AllowImplicitTypename = ImplicitTypenameContext::No;
if (D.getCXXScopeSpec().isSet())
AllowImplicitTypename =
(ImplicitTypenameContext)Actions.isDeclaratorFunctionLike(D);
else if (D.getContext() == DeclaratorContext::Member) {
AllowImplicitTypename = ImplicitTypenameContext::Yes;
}
// The name of the declarator, if any, is tentatively declared within
// a possible direct initializer.
TentativelyDeclaredIdentifiers.push_back(D.getIdentifier());
bool IsFunctionDecl =
isCXXFunctionDeclarator(&IsAmbiguous, AllowImplicitTypename);
TentativelyDeclaredIdentifiers.pop_back();
if (!IsFunctionDecl)
break;
}
ParsedAttributes attrs(AttrFactory);
BalancedDelimiterTracker T(*this, tok::l_paren);
T.consumeOpen();
if (IsFunctionDeclaration)
Actions.ActOnStartFunctionDeclarationDeclarator(D,
TemplateParameterDepth);
ParseFunctionDeclarator(D, attrs, T, IsAmbiguous);
if (IsFunctionDeclaration)
Actions.ActOnFinishFunctionDeclarationDeclarator(D);
PrototypeScope.Exit();
} else if (Tok.is(tok::l_square)) {
ParseBracketDeclarator(D);
} else if (Tok.is(tok::kw_requires) && D.hasGroupingParens()) {
// This declarator is declaring a function, but the requires clause is
// in the wrong place:
// void (f() requires true);
// instead of
// void f() requires true;
// or
// void (f()) requires true;
Diag(Tok, diag::err_requires_clause_inside_parens);
ConsumeToken();
ExprResult TrailingRequiresClause = Actions.CorrectDelayedTyposInExpr(
ParseConstraintLogicalOrExpression(/*IsTrailingRequiresClause=*/true));
if (TrailingRequiresClause.isUsable() && D.isFunctionDeclarator() &&
!D.hasTrailingRequiresClause())
// We're already ill-formed if we got here but we'll accept it anyway.
D.setTrailingRequiresClause(TrailingRequiresClause.get());
} else {
break;
}
}
}

and let then sema to handle the error - error: friends can only be classes or function
here -
NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
MultiTemplateParamsArg TemplateParams) {
const DeclSpec &DS = D.getDeclSpec();
assert(DS.isFriendSpecified());
assert(DS.getStorageClassSpec() == DeclSpec::SCS_unspecified);
SourceLocation Loc = D.getIdentifierLoc();
TypeSourceInfo *TInfo = GetTypeForDeclarator(D, S);
// C++ [class.friend]p1
// A friend of a class is a function or class....
// Note that this sees through typedefs, which is intended.
// It *doesn't* see through dependent types, which is correct
// according to [temp.arg.type]p3:
// If a declaration acquires a function type through a
// type dependent on a template-parameter and this causes
// a declaration that does not use the syntactic form of a
// function declarator to have a function type, the program
// is ill-formed.
if (!TInfo->getType()->isFunctionType()) {
Diag(Loc, diag::err_unexpected_friend);
// It might be worthwhile to try to recover by creating an
// appropriate declaration.
return nullptr;
}
// C++ [namespace.memdef]p3
// - If a friend declaration in a non-local class first declares a
// class or function, the friend class or function is a member
// of the innermost enclosing namespace.
// - The name of the friend is not found by simple name lookup
// until a matching declaration is provided in that namespace
// scope (either before or after the class declaration granting
// friendship).
// - If a friend function is called, its name may be found by the
// name lookup that considers functions from namespaces and
// classes associated with the types of the function arguments.
// - When looking for a prior declaration of a class or a function
// declared as a friend, scopes outside the innermost enclosing
// namespace scope are not considered.
CXXScopeSpec &SS = D.getCXXScopeSpec();
DeclarationNameInfo NameInfo = GetNameForDeclarator(D);
assert(NameInfo.getName());
// Check for unexpanded parameter packs.
if (DiagnoseUnexpandedParameterPack(Loc, TInfo, UPPC_FriendDeclaration) ||
DiagnoseUnexpandedParameterPack(NameInfo, UPPC_FriendDeclaration) ||
DiagnoseUnexpandedParameterPack(SS, UPPC_FriendDeclaration))
return nullptr;
// The context we found the declaration in, or in which we should
// create the declaration.
DeclContext *DC;
Scope *DCScope = S;
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
ForExternalRedeclaration);
// There are five cases here.
// - There's no scope specifier and we're in a local class. Only look
// for functions declared in the immediately-enclosing block scope.
// We recover from invalid scope qualifiers as if they just weren't there.
FunctionDecl *FunctionContainingLocalClass = nullptr;
if ((SS.isInvalid() || !SS.isSet()) &&
(FunctionContainingLocalClass =
cast<CXXRecordDecl>(CurContext)->isLocalClass())) {
// C++11 [class.friend]p11:
// If a friend declaration appears in a local class and the name
// specified is an unqualified name, a prior declaration is
// looked up without considering scopes that are outside the
// innermost enclosing non-class scope. For a friend function
// declaration, if there is no prior declaration, the program is
// ill-formed.
// Find the innermost enclosing non-class scope. This is the block
// scope containing the local class definition (or for a nested class,
// the outer local class).
DCScope = S->getFnParent();
// Look up the function name in the scope.
Previous.clear(LookupLocalFriendName);
LookupName(Previous, S, /*AllowBuiltinCreation*/false);
if (!Previous.empty()) {
// All possible previous declarations must have the same context:
// either they were declared at block scope or they are members of
// one of the enclosing local classes.
DC = Previous.getRepresentativeDecl()->getDeclContext();
} else {
// This is ill-formed, but provide the context that we would have
// declared the function in, if we were permitted to, for error recovery.
DC = FunctionContainingLocalClass;
}
adjustContextForLocalExternDecl(DC);
// C++ [class.friend]p6:
// A function can be defined in a friend declaration of a class if and
// only if the class is a non-local class (9.8), the function name is
// unqualified, and the function has namespace scope.
if (D.isFunctionDefinition()) {
Diag(NameInfo.getBeginLoc(), diag::err_friend_def_in_local_class);
}
// - There's no scope specifier, in which case we just go to the
// appropriate scope and look for a function or function template
// there as appropriate.
} else if (SS.isInvalid() || !SS.isSet()) {
// C++11 [namespace.memdef]p3:
// If the name in a friend declaration is neither qualified nor
// a template-id and the declaration is a function or an
// elaborated-type-specifier, the lookup to determine whether
// the entity has been previously declared shall not consider
// any scopes outside the innermost enclosing namespace.
bool isTemplateId =
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId;
// Find the appropriate context according to the above.
DC = CurContext;
// Skip class contexts. If someone can cite chapter and verse
// for this behavior, that would be nice --- it's what GCC and
// EDG do, and it seems like a reasonable intent, but the spec
// really only says that checks for unqualified existing
// declarations should stop at the nearest enclosing namespace,
// not that they should only consider the nearest enclosing
// namespace.
while (DC->isRecord())
DC = DC->getParent();
DeclContext *LookupDC = DC->getNonTransparentContext();
while (true) {
LookupQualifiedName(Previous, LookupDC);
if (!Previous.empty()) {
DC = LookupDC;
break;
}
if (isTemplateId) {
if (isa<TranslationUnitDecl>(LookupDC)) break;
} else {
if (LookupDC->isFileContext()) break;
}
LookupDC = LookupDC->getParent();
}
DCScope = getScopeForDeclContext(S, DC);
// - There's a non-dependent scope specifier, in which case we
// compute it and do a previous lookup there for a function
// or function template.
} else if (!SS.getScopeRep()->isDependent()) {
DC = computeDeclContext(SS);
if (!DC) return nullptr;
if (RequireCompleteDeclContext(SS, DC)) return nullptr;
LookupQualifiedName(Previous, DC);
// C++ [class.friend]p1: A friend of a class is a function or
// class that is not a member of the class . . .
if (DC->Equals(CurContext))
Diag(DS.getFriendSpecLoc(),
getLangOpts().CPlusPlus11 ?
diag::warn_cxx98_compat_friend_is_member :
diag::err_friend_is_member);
if (D.isFunctionDefinition()) {
// C++ [class.friend]p6:
// A function can be defined in a friend declaration of a class if and
// only if the class is a non-local class (9.8), the function name is
// unqualified, and the function has namespace scope.
//
// FIXME: We should only do this if the scope specifier names the
// innermost enclosing namespace; otherwise the fixit changes the
// meaning of the code.
SemaDiagnosticBuilder DB
= Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def);
DB << SS.getScopeRep();
if (DC->isFileContext())
DB << FixItHint::CreateRemoval(SS.getRange());
SS.clear();
}
// - There's a scope specifier that does not match any template
// parameter lists, in which case we use some arbitrary context,
// create a method or method template, and wait for instantiation.
// - There's a scope specifier that does match some template
// parameter lists, which we don't handle right now.
} else {
if (D.isFunctionDefinition()) {
// C++ [class.friend]p6:
// A function can be defined in a friend declaration of a class if and
// only if the class is a non-local class (9.8), the function name is
// unqualified, and the function has namespace scope.
Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def)
<< SS.getScopeRep();
}
DC = CurContext;
assert(isa<CXXRecordDecl>(DC) && "friend declaration not in class?");
}
if (!DC->isRecord()) {
int DiagArg = -1;
switch (D.getName().getKind()) {
case UnqualifiedIdKind::IK_ConstructorTemplateId:
case UnqualifiedIdKind::IK_ConstructorName:
DiagArg = 0;
break;
case UnqualifiedIdKind::IK_DestructorName:
DiagArg = 1;
break;
case UnqualifiedIdKind::IK_ConversionFunctionId:
DiagArg = 2;
break;
case UnqualifiedIdKind::IK_DeductionGuideName:
DiagArg = 3;
break;
case UnqualifiedIdKind::IK_Identifier:
case UnqualifiedIdKind::IK_ImplicitSelfParam:
case UnqualifiedIdKind::IK_LiteralOperatorId:
case UnqualifiedIdKind::IK_OperatorFunctionId:
case UnqualifiedIdKind::IK_TemplateId:
break;
}
// This implies that it has to be an operator or function.
if (DiagArg >= 0) {
Diag(Loc, diag::err_introducing_special_friend) << DiagArg;
return nullptr;
}
}
// FIXME: This is an egregious hack to cope with cases where the scope stack
// does not contain the declaration context, i.e., in an out-of-line
// definition of a class.
Scope FakeDCScope(S, Scope::DeclScope, Diags);
if (!DCScope) {
FakeDCScope.setEntity(DC);
DCScope = &FakeDCScope;
}
bool AddToScope = true;
NamedDecl *ND = ActOnFunctionDeclarator(DCScope, D, DC, TInfo, Previous,
TemplateParams, AddToScope);
if (!ND) return nullptr;
assert(ND->getLexicalDeclContext() == CurContext);
// If we performed typo correction, we might have added a scope specifier
// and changed the decl context.
DC = ND->getDeclContext();
// Add the function declaration to the appropriate lookup tables,
// adjusting the redeclarations list as necessary. We don't
// want to do this yet if the friending class is dependent.
//
// Also update the scope-based lookup if the target context's
// lookup context is in lexical scope.
if (!CurContext->isDependentContext()) {
DC = DC->getRedeclContext();
DC->makeDeclVisibleInContext(ND);
if (Scope *EnclosingScope = getScopeForDeclContext(S, DC))
PushOnScopeChains(ND, EnclosingScope, /*AddToContext=*/ false);
}
FriendDecl *FrD = FriendDecl::Create(Context, CurContext,
D.getIdentifierLoc(), ND,
DS.getFriendSpecLoc());
FrD->setAccess(AS_public);
CurContext->addDecl(FrD);
if (ND->isInvalidDecl()) {
FrD->setInvalidDecl();
} else {
if (DC->isRecord()) CheckFriendAccess(ND);
FunctionDecl *FD;
if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(ND))
FD = FTD->getTemplatedDecl();
else
FD = cast<FunctionDecl>(ND);
// C++11 [dcl.fct.default]p4: If a friend declaration specifies a
// default argument expression, that declaration shall be a definition
// and shall be the only declaration of the function or function
// template in the translation unit.
if (functionDeclHasDefaultArgument(FD)) {
// We can't look at FD->getPreviousDecl() because it may not have been set
// if we're in a dependent context. If the function is known to be a
// redeclaration, we will have narrowed Previous down to the right decl.
if (D.isRedeclaration()) {
Diag(FD->getLocation(), diag::err_friend_decl_with_def_arg_redeclared);
Diag(Previous.getRepresentativeDecl()->getLocation(),
diag::note_previous_declaration);
} else if (!D.isFunctionDefinition())
Diag(FD->getLocation(), diag::err_friend_decl_with_def_arg_must_be_def);
}
// Mark templated-scope function declarations as unsupported.
if (FD->getNumTemplateParameterLists() && SS.isValid()) {
Diag(FD->getLocation(), diag::warn_template_qualified_friend_unsupported)
<< SS.getScopeRep() << SS.getRange()
<< cast<CXXRecordDecl>(CurContext);
FrD->setUnsupportedFriend(true);
}
}
warnOnReservedIdentifier(ND);
return ND;
}

@xgupta xgupta removed their assignment Feb 5, 2023
@cor3ntin
Copy link
Contributor

@Sirraide any chance you want to look at this one?

@Sirraide
Copy link
Member

Having looked at the code that parses friend declarations recently, I should probably be able to figure out where to diagnose that.

@Sirraide Sirraide self-assigned this Aug 15, 2024
cjdb pushed a commit to cjdb/llvm-project that referenced this issue Aug 23, 2024
Diagnose this early after parsing declaration specifiers; this allows us
to issue a better diagnostic. This also checks for `concept friend` and
concept declarations w/o a template-head because it’s easiest to do that
at the same time.

Fixes llvm#45182.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugzilla Issues migrated from bugzilla c++20 clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer concepts C++20 concepts confirmed Verified by a second party
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants