PR42694 Support explicit(bool) in older language modes as an extension.

This needs somewhat careful disambiguation, as C++2a explicit(bool) is a
breaking change. We only enable it in cases where the source construct
could not possibly be anything else.
This commit is contained in:
Richard Smith
2020-01-15 18:37:32 -08:00
parent 154cd6de51
commit 45d70806f4
6 changed files with 91 additions and 16 deletions

View File

@@ -33,10 +33,6 @@ def err_asm_goto_cannot_have_output : Error<
let CategoryName = "Parse Issue" in {
def warn_cxx2a_compat_explicit_bool : Warning<
"this expression will be parsed as explicit(bool) in C++2a">,
InGroup<CXX2aCompat>, DefaultIgnore;
def ext_empty_translation_unit : Extension<
"ISO C requires a translation unit to contain at least one declaration">,
InGroup<DiagGroup<"empty-translation-unit">>;
@@ -684,6 +680,15 @@ def err_ms_property_expected_comma_or_rparen : Error<
def err_ms_property_initializer : Error<
"property declaration cannot have an in-class initializer">;
def warn_cxx2a_compat_explicit_bool : Warning<
"this expression will be parsed as explicit(bool) in C++2a">,
InGroup<CXX2aCompat>, DefaultIgnore;
def warn_cxx17_compat_explicit_bool : Warning<
"explicit(bool) is incompatible with C++ standards before C++2a">,
InGroup<CXXPre2aCompat>, DefaultIgnore;
def ext_explicit_bool : ExtWarn<"explicit(bool) is a C++2a extension">,
InGroup<CXX2a>;
/// C++ Templates
def err_expected_template : Error<"expected template">;
def err_unknown_template_name : Error<

View File

@@ -806,6 +806,16 @@ public:
bool IsNewScope);
bool TryAnnotateCXXScopeToken(bool EnteringContext = false);
bool MightBeCXXScopeToken() {
return Tok.is(tok::identifier) || Tok.is(tok::coloncolon) ||
(Tok.is(tok::annot_template_id) &&
NextToken().is(tok::coloncolon)) ||
Tok.is(tok::kw_decltype) || Tok.is(tok::kw___super);
}
bool TryAnnotateOptionalCXXScopeToken(bool EnteringContext = false) {
return MightBeCXXScopeToken() && TryAnnotateCXXScopeToken(EnteringContext);
}
private:
enum AnnotatedNameKind {
/// Annotation has failed and emitted an error.
@@ -2395,6 +2405,11 @@ private:
/// rather than a less-than expression.
TPResult isTemplateArgumentList(unsigned TokensToSkip);
/// Determine whether an '(' after an 'explicit' keyword is part of a C++20
/// 'explicit(bool)' declaration, in earlier language modes where that is an
/// extension.
TPResult isExplicitBool();
/// Determine whether an identifier has been tentatively declared as a
/// non-type. Such tentative declarations should not be found to name a type
/// during a tentative parse, but also should not be annotated as a non-type.

View File

@@ -3617,7 +3617,11 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
ConsumedEnd = ExplicitLoc;
ConsumeToken(); // kw_explicit
if (Tok.is(tok::l_paren)) {
if (getLangOpts().CPlusPlus2a) {
if (getLangOpts().CPlusPlus2a || isExplicitBool() == TPResult::True) {
Diag(Tok.getLocation(), getLangOpts().CPlusPlus2a
? diag::warn_cxx17_compat_explicit_bool
: diag::ext_explicit_bool);
ExprResult ExplicitExpr(static_cast<Expr *>(nullptr));
BalancedDelimiterTracker Tracker(*this, tok::l_paren);
Tracker.consumeOpen();
@@ -3630,8 +3634,9 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
Actions.ActOnExplicitBoolSpecifier(ExplicitExpr.get());
} else
Tracker.skipToEnd();
} else
} else {
Diag(Tok.getLocation(), diag::warn_cxx2a_compat_explicit_bool);
}
}
isInvalid = DS.setFunctionSpecExplicit(ExplicitLoc, PrevSpec, DiagID,
ExplicitSpec, CloseParenLoc);

View File

@@ -202,9 +202,7 @@ Parser::TPResult Parser::TryConsumeDeclarationSpecifier() {
}
}
if (Tok.isOneOf(tok::identifier, tok::coloncolon, tok::kw_decltype,
tok::annot_template_id) &&
TryAnnotateCXXScopeToken())
if (TryAnnotateOptionalCXXScopeToken())
return TPResult::Error;
if (Tok.is(tok::annot_cxxscope))
ConsumeAnnotationToken();
@@ -785,9 +783,8 @@ Parser::isCXX11AttributeSpecifier(bool Disambiguate,
Parser::TPResult Parser::TryParsePtrOperatorSeq() {
while (true) {
if (Tok.isOneOf(tok::coloncolon, tok::identifier))
if (TryAnnotateCXXScopeToken(true))
return TPResult::Error;
if (TryAnnotateOptionalCXXScopeToken(true))
return TPResult::Error;
if (Tok.isOneOf(tok::star, tok::amp, tok::caret, tok::ampamp) ||
(Tok.is(tok::annot_cxxscope) && NextToken().is(tok::star))) {
@@ -2137,3 +2134,58 @@ Parser::TPResult Parser::isTemplateArgumentList(unsigned TokensToSkip) {
return TPResult::Ambiguous;
return TPResult::False;
}
/// Determine whether we might be looking at the '(' of a C++20 explicit(bool)
/// in an earlier language mode.
Parser::TPResult Parser::isExplicitBool() {
assert(Tok.is(tok::l_paren) && "expected to be looking at a '(' token");
RevertingTentativeParsingAction PA(*this);
ConsumeParen();
// We can only have 'explicit' on a constructor, conversion function, or
// deduction guide. The declarator of a deduction guide cannot be
// parenthesized, so we know this isn't a deduction guide. So the only
// thing we need to check for is some number of parens followed by either
// the current class name or 'operator'.
while (Tok.is(tok::l_paren))
ConsumeParen();
if (TryAnnotateOptionalCXXScopeToken())
return TPResult::Error;
// Class-scope constructor and conversion function names can't really be
// qualified, but we get better diagnostics if we assume they can be.
CXXScopeSpec SS;
if (Tok.is(tok::annot_cxxscope)) {
Actions.RestoreNestedNameSpecifierAnnotation(Tok.getAnnotationValue(),
Tok.getAnnotationRange(),
SS);
ConsumeAnnotationToken();
}
// 'explicit(operator' might be explicit(bool) or the declaration of a
// conversion function, but it's probably a conversion function.
if (Tok.is(tok::kw_operator))
return TPResult::Ambiguous;
// If this can't be a constructor name, it can only be explicit(bool).
if (Tok.isNot(tok::identifier) && Tok.isNot(tok::annot_template_id))
return TPResult::True;
if (!Actions.isCurrentClassName(Tok.is(tok::identifier)
? *Tok.getIdentifierInfo()
: *takeTemplateIdAnnotation(Tok)->Name,
getCurScope(), &SS))
return TPResult::True;
// Formally, we must have a right-paren after the constructor name to match
// the grammar for a constructor. But clang permits a parenthesized
// constructor declarator, so also allow a constructor declarator to follow
// with no ')' token after the constructor name.
if (!NextToken().is(tok::r_paren) &&
!isConstructorDeclarator(/*Unqualified=*/SS.isEmpty(),
/*DeductionGuide=*/false))
return TPResult::True;
// Might be explicit(bool) or a parenthesized constructor name.
return TPResult::Ambiguous;
}

View File

@@ -2005,10 +2005,7 @@ bool Parser::TryAnnotateTypeOrScopeTokenAfterScopeSpec(CXXScopeSpec &SS,
bool Parser::TryAnnotateCXXScopeToken(bool EnteringContext) {
assert(getLangOpts().CPlusPlus &&
"Call sites of this function should be guarded by checking for C++");
assert((Tok.is(tok::identifier) || Tok.is(tok::coloncolon) ||
(Tok.is(tok::annot_template_id) && NextToken().is(tok::coloncolon)) ||
Tok.is(tok::kw_decltype) || Tok.is(tok::kw___super)) &&
"Cannot be a type or scope token!");
assert(MightBeCXXScopeToken() && "Cannot be a type or scope token!");
CXXScopeSpec SS;
if (ParseOptionalCXXScopeSpecifier(SS, nullptr, EnteringContext))

View File

@@ -1,3 +1,4 @@
// RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify -Wno-c++2a-extensions
// RUN: %clang_cc1 -std=c++2a -fsyntax-only %s -verify
template <bool b, auto val> struct enable_ifv {};