public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r12-6563] c++: warning for dependent template members [PR70417]
@ 2022-01-13 21:02 Jason Merrill
0 siblings, 0 replies; only message in thread
From: Jason Merrill @ 2022-01-13 21:02 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:b8ffa71e4271ae562c2d315b9b24c4979bbf8227
commit r12-6563-gb8ffa71e4271ae562c2d315b9b24c4979bbf8227
Author: Anthony Sharp <anthonysharp15@gmail.com>
Date: Sat Dec 4 17:23:22 2021 +0000
c++: warning for dependent template members [PR70417]
Add a helpful warning message for when the user forgets to
include the "template" keyword after ., -> or :: when
accessing a member in a dependent context, where the member is a
template.
PR c++/70417
gcc/c-family/ChangeLog:
* c.opt: Added -Wmissing-template-keyword.
gcc/cp/ChangeLog:
* parser.c (cp_parser_id_expression): Handle
-Wmissing-template-keyword.
(struct saved_token_sentinel): Add modes to control what happens
on destruction.
(cp_parser_statement): Adjust.
(cp_parser_skip_entire_template_parameter_list): New function that
skips an entire template parameter list.
(cp_parser_require_end_of_template_parameter_list): Rename old
cp_parser_skip_to_end_of_template_parameter_list.
(cp_parser_skip_to_end_of_template_parameter_list): Refactor to be
called from one of the above two functions.
(cp_parser_lambda_declarator_opt)
(cp_parser_explicit_template_declaration)
(cp_parser_enclosed_template_argument_list): Adjust.
gcc/ChangeLog:
* doc/invoke.texi: Documentation for Wmissing-template-keyword.
gcc/testsuite/ChangeLog:
* g++.dg/cpp0x/variadic-mem_fn2.C: Catch warning about missing
template keyword.
* g++.dg/template/dependent-name17.C: New test.
* g++.dg/template/dependent-name18.C: New test.
Co-authored-by: Jason Merrill <jason@redhat.com>
Diff:
---
gcc/doc/invoke.texi | 33 +++++
gcc/c-family/c.opt | 4 +
gcc/cp/parser.c | 178 +++++++++++++++++------
gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C | 1 +
gcc/testsuite/g++.dg/template/dependent-name17.C | 49 +++++++
gcc/testsuite/g++.dg/template/dependent-name18.C | 5 +
6 files changed, 223 insertions(+), 47 deletions(-)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6b84228f142..5504971ea81 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -8963,6 +8963,39 @@ type @samp{T}.
This warning can be disabled with @option{-Wno-missing-requires}.
+@item -Wno-missing-template-keyword
+@opindex Wmissing-template-keyword
+@opindex Wno-missing-template-keyword
+
+The member access tokens ., -> and :: must be followed by the @code{template}
+keyword if the parent object is dependent and the member being named is a
+template.
+
+@smallexample
+template <class X>
+void DoStuff (X x)
+@{
+ x.template DoSomeOtherStuff<X>(); // Good.
+ x.DoMoreStuff<X>(); // Warning, x is dependent.
+@}
+@end smallexample
+
+In rare cases it is possible to get false positives. To silence this, wrap
+the expression in parentheses. For example, the following is treated as a
+template, even where m and N are integers:
+
+@smallexample
+void NotATemplate (my_class t)
+@{
+ int N = 5;
+
+ bool test = t.m < N > (0); // Treated as a template.
+ test = (t.m < N) > (0); // Same meaning, but not treated as a template.
+@}
+@end smallexample
+
+This warning can be disabled with @option{-Wno-missing-template-keyword}.
+
@item -Wno-multichar
@opindex Wno-multichar
@opindex Wmultichar
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 21fb5f7ebbc..dcda1cccedd 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -880,6 +880,10 @@ Wmissing-requires
C++ ObjC++ Var(warn_missing_requires) Init(1) Warning
Warn about likely missing requires keyword.
+Wmissing-template-keyword
+C++ ObjC++ Var(warn_missing_template_keyword) Init(1) Warning
+Warn when the template keyword is missing after a member access token in a dependent member access expression if that member is a template.
+
Wmultistatement-macros
C ObjC C++ ObjC++ Var(warn_multistatement_macros) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
Warn about unsafe macros expanding to multiple statements used as a body of a clause such as if, else, while, switch, or for.
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index f40e707d47c..c06ed5e4dbd 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -1275,17 +1275,32 @@ cp_lexer_rollback_tokens (cp_lexer* lexer)
lexer->next_token = lexer->saved_tokens.pop ();
}
-/* RAII wrapper around the above functions, with sanity checking. Creating
- a variable saves tokens, which are committed when the variable is
- destroyed unless they are explicitly rolled back by calling the rollback
- member function. */
+/* Determines what saved_token_sentinel does when going out of scope. */
+
+enum saved_token_sentinel_mode {
+ STS_COMMIT,
+ STS_ROLLBACK,
+ STS_DONOTHING
+};
+
+/* RAII wrapper around the above functions, with sanity checking (the token
+ stream should be the same at the point of instantiation as it is at the
+ point of destruction).
+
+ Creating a variable saves tokens. MODE determines what happens when the
+ object is destroyed. STS_COMMIT commits tokens (default),
+ STS_ROLLBACK rolls-back and STS_DONOTHING does nothing. Calling
+ rollback() will immediately roll-back tokens and set MODE to
+ STS_DONOTHING. */
struct saved_token_sentinel
{
cp_lexer *lexer;
unsigned len;
- bool commit;
- saved_token_sentinel(cp_lexer *lexer): lexer(lexer), commit(true)
+ saved_token_sentinel_mode mode;
+ saved_token_sentinel (cp_lexer *_lexer,
+ saved_token_sentinel_mode _mode = STS_COMMIT)
+ : lexer (_lexer), mode (_mode)
{
len = lexer->saved_tokens.length ();
cp_lexer_save_tokens (lexer);
@@ -1293,12 +1308,15 @@ struct saved_token_sentinel
void rollback ()
{
cp_lexer_rollback_tokens (lexer);
- commit = false;
+ mode = STS_DONOTHING;
}
- ~saved_token_sentinel()
+ ~saved_token_sentinel ()
{
- if (commit)
+ if (mode == STS_COMMIT)
cp_lexer_commit_tokens (lexer);
+ else if (mode == STS_ROLLBACK)
+ cp_lexer_rollback_tokens (lexer);
+
gcc_assert (lexer->saved_tokens.length () == len);
}
};
@@ -2771,7 +2789,11 @@ static void cp_parser_skip_to_end_of_block_or_statement
(cp_parser *);
static bool cp_parser_skip_to_closing_brace
(cp_parser *);
-static void cp_parser_skip_to_end_of_template_parameter_list
+static bool cp_parser_skip_entire_template_parameter_list
+ (cp_parser *);
+static void cp_parser_require_end_of_template_parameter_list
+ (cp_parser *);
+static bool cp_parser_skip_to_end_of_template_parameter_list
(cp_parser *);
static void cp_parser_skip_to_pragma_eol
(cp_parser*, cp_token *);
@@ -6129,47 +6151,41 @@ cp_parser_id_expression (cp_parser *parser,
template_keyword_p)
!= NULL_TREE);
+ cp_expr id = NULL_TREE;
+ tree scope = parser->scope;
+
+ /* Peek at the next token. */
+ cp_token *token = cp_lexer_peek_token (parser->lexer);
+
/* If there is a nested-name-specifier, then we are looking at
the first qualified-id production. */
if (nested_name_specifier_p)
{
- tree saved_scope;
tree saved_object_scope;
tree saved_qualifying_scope;
- cp_expr unqualified_id;
- bool is_template;
/* See if the next token is the `template' keyword. */
if (!template_p)
- template_p = &is_template;
+ template_p = &template_keyword_p;
*template_p = cp_parser_optional_template_keyword (parser);
/* Name lookup we do during the processing of the
unqualified-id might obliterate SCOPE. */
- saved_scope = parser->scope;
saved_object_scope = parser->object_scope;
saved_qualifying_scope = parser->qualifying_scope;
/* Process the final unqualified-id. */
- unqualified_id = cp_parser_unqualified_id (parser, *template_p,
- check_dependency_p,
- declarator_p,
- /*optional_p=*/false);
+ id = cp_parser_unqualified_id (parser, *template_p,
+ check_dependency_p,
+ declarator_p,
+ /*optional_p=*/false);
/* Restore the SAVED_SCOPE for our caller. */
- parser->scope = saved_scope;
+ parser->scope = scope;
parser->object_scope = saved_object_scope;
parser->qualifying_scope = saved_qualifying_scope;
-
- return unqualified_id;
}
/* Otherwise, if we are in global scope, then we are looking at one
of the other qualified-id productions. */
else if (global_scope_p)
{
- cp_token *token;
- tree id;
-
- /* Peek at the next token. */
- token = cp_lexer_peek_token (parser->lexer);
-
/* If it's an identifier, and the next token is not a "<", then
we can avoid the template-id case. This is an optimization
for this common case. */
@@ -6195,11 +6211,15 @@ cp_parser_id_expression (cp_parser *parser,
switch (token->type)
{
case CPP_NAME:
- return cp_parser_identifier (parser);
+ id = cp_parser_identifier (parser);
+ break;
case CPP_KEYWORD:
if (token->keyword == RID_OPERATOR)
- return cp_parser_operator_function_id (parser);
+ {
+ id = cp_parser_operator_function_id (parser);
+ break;
+ }
/* Fall through. */
default:
@@ -6208,10 +6228,44 @@ cp_parser_id_expression (cp_parser *parser,
}
}
else
- return cp_parser_unqualified_id (parser, template_keyword_p,
+ {
+ if (!scope)
+ scope = parser->context->object_type;
+ id = cp_parser_unqualified_id (parser, template_keyword_p,
/*check_dependency_p=*/true,
declarator_p,
optional_p);
+ }
+
+ if (id && TREE_CODE (id) == IDENTIFIER_NODE
+ && warn_missing_template_keyword
+ && !template_keyword_p
+ /* Don't warn if we're looking inside templates. */
+ && check_dependency_p
+ /* In a template argument list a > could be closing
+ the enclosing targs. */
+ && !parser->in_template_argument_list_p
+ && scope && dependentish_scope_p (scope)
+ /* Don't confuse an ill-formed constructor declarator for a missing
+ template keyword in a return type. */
+ && !(declarator_p && constructor_name_p (id, scope))
+ && cp_parser_nth_token_starts_template_argument_list_p (parser, 1)
+ && warning_enabled_at (token->location,
+ OPT_Wmissing_template_keyword))
+ {
+ saved_token_sentinel toks (parser->lexer, STS_ROLLBACK);
+ if (cp_parser_skip_entire_template_parameter_list (parser)
+ /* An operator after the > suggests that the > ends a
+ template-id; a name or literal suggests that the > is an
+ operator. */
+ && (cp_lexer_peek_token (parser->lexer)->type
+ <= CPP_LAST_PUNCTUATOR))
+ warning_at (token->location, OPT_Wmissing_template_keyword,
+ "expected %qs keyword before dependent "
+ "template name", "template");
+ }
+
+ return id;
}
/* Parse an unqualified-id.
@@ -11472,7 +11526,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
cp_lexer_consume_token (parser->lexer);
template_param_list = cp_parser_template_parameter_list (parser);
- cp_parser_skip_to_end_of_template_parameter_list (parser);
+ cp_parser_require_end_of_template_parameter_list (parser);
/* We may have a constrained generic lambda; parse the requires-clause
immediately after the template-parameter-list and combine with any
@@ -12353,11 +12407,11 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr,
{
if (saved_tokens.lexer == lexer)
{
- if (saved_tokens.commit)
+ if (saved_tokens.mode == STS_COMMIT)
cp_lexer_commit_tokens (lexer);
gcc_assert (lexer->saved_tokens.length () == saved_tokens.len);
saved_tokens.lexer = parser->lexer;
- saved_tokens.commit = false;
+ saved_tokens.mode = STS_DONOTHING;
saved_tokens.len = parser->lexer->saved_tokens.length ();
}
cp_lexer_destroy (lexer);
@@ -31493,7 +31547,7 @@ cp_parser_explicit_template_declaration (cp_parser* parser, bool member_p)
}
/* Look for the `>'. */
- cp_parser_skip_to_end_of_template_parameter_list (parser);
+ cp_parser_require_end_of_template_parameter_list (parser);
/* Manage template requirements */
if (flag_concepts)
@@ -32017,7 +32071,7 @@ cp_parser_enclosed_template_argument_list (cp_parser* parser)
}
}
else
- cp_parser_skip_to_end_of_template_parameter_list (parser);
+ cp_parser_require_end_of_template_parameter_list (parser);
/* The `>' token might be a greater-than operator again now. */
parser->greater_than_is_operator_p
= saved_greater_than_is_operator_p;
@@ -32912,11 +32966,45 @@ cp_parser_require (cp_parser* parser,
}
}
-/* An error message is produced if the next token is not '>'.
- All further tokens are skipped until the desired token is
- found or '{', '}', ';' or an unbalanced ')' or ']'. */
+/* Skip an entire parameter list from start to finish. The next token must
+ be the initial "<" of the parameter list. Returns true on success and
+ false otherwise. */
+
+static bool
+cp_parser_skip_entire_template_parameter_list (cp_parser* parser)
+{
+ /* Consume the "<" because cp_parser_skip_to_end_of_template_parameter_list
+ requires it. */
+ cp_lexer_consume_token (parser->lexer);
+ return cp_parser_skip_to_end_of_template_parameter_list (parser);
+}
+
+/* Ensure we are at the end of a template parameter list. If we are, return.
+ If we are not, something has gone wrong, in which case issue an error and
+ skip to end of the parameter list. */
static void
+cp_parser_require_end_of_template_parameter_list (cp_parser* parser)
+{
+ /* Are we ready, yet? If not, issue error message. */
+ if (cp_parser_require (parser, CPP_GREATER, RT_GREATER))
+ return;
+
+ cp_parser_skip_to_end_of_template_parameter_list (parser);
+}
+
+/* You should only call this function from inside a template parameter list
+ (i.e. the current token should at least be the initial "<" of the
+ parameter list). If you are skipping the entire list, it may be better to
+ use cp_parser_skip_entire_template_parameter_list.
+
+ Tokens are skipped until the final ">" is found, or if we see
+ '{', '}', ';', or if we find an unbalanced ')' or ']'.
+
+ Returns true if we successfully reached the end, and false if
+ something unexpected happened (e.g. end of file). */
+
+static bool
cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
{
/* Current level of '< ... >'. */
@@ -32924,10 +33012,6 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
/* Ignore '<' and '>' nested inside '( ... )' or '[ ... ]'. */
unsigned nesting_depth = 0;
- /* Are we ready, yet? If not, issue error message. */
- if (cp_parser_require (parser, CPP_GREATER, RT_GREATER))
- return;
-
/* Skip tokens until the desired token is found. */
while (true)
{
@@ -32951,7 +33035,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
spurious. Just consume the `>>' and stop; we've
already produced at least one error. */
cp_lexer_consume_token (parser->lexer);
- return;
+ return false;
}
/* Fall through for C++0x, so we handle the second `>' in
the `>>'. */
@@ -32962,7 +33046,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
{
/* We've reached the token we want, consume it and stop. */
cp_lexer_consume_token (parser->lexer);
- return;
+ return true;
}
break;
@@ -32974,7 +33058,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
case CPP_CLOSE_PAREN:
case CPP_CLOSE_SQUARE:
if (nesting_depth-- == 0)
- return;
+ return false;
break;
case CPP_EOF:
@@ -32983,7 +33067,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser)
case CPP_OPEN_BRACE:
case CPP_CLOSE_BRACE:
/* The '>' was probably forgotten, don't look further. */
- return;
+ return false;
default:
break;
diff --git a/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C b/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C
index 4a02ab22990..c343f32086d 100644
--- a/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C
+++ b/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C
@@ -5,5 +5,6 @@ template <class A0, class... As> struct tuple
tuple<As...> tail;
template <int Offset, class... More> int apply(const More&... more) {
return tail.apply<1>(more...); // { dg-error "" } needs .template
+ // { dg-warning "keyword before dependent template name" "" { target *-*-* } .-1 }
}
};
diff --git a/gcc/testsuite/g++.dg/template/dependent-name17.C b/gcc/testsuite/g++.dg/template/dependent-name17.C
new file mode 100644
index 00000000000..ff623e2d2ba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/dependent-name17.C
@@ -0,0 +1,49 @@
+// C++ PR 70317
+// { dg-do compile }
+// { dg-prune-output "expected primary-expression" }
+
+template<class T> class mytemplateclass
+{
+public:
+ template<class U> void class_func() {}
+ template<class U> static void class_func_static() {}
+};
+
+class myclass
+{
+public:
+ int testint;
+ template<class U> void class_func() {}
+ template<class U> static void class_func_static() {}
+};
+
+template<class Y> void tests_func(mytemplateclass<Y> c, myclass c2)
+{
+ /* Dependent template accessors (ill-formed code). */
+ c.class_func<Y>(); // { dg-warning "keyword before dependent template name" }
+ (&c)->class_func<Y>(); // { dg-warning "keyword before dependent template name" }
+ mytemplateclass<Y>::class_func_static<Y>(); // { dg-warning "keyword before dependent template name" }
+
+ /* Dependent template accessors (well-formed code). */
+ c.template class_func<Y>();
+ (&c)->template class_func<Y>();
+ mytemplateclass<Y>::template class_func_static<Y>();
+
+ /* Non-dependent template accessors (well-formed code). */
+ c2.class_func<myclass>();
+ (&c2)->class_func<myclass>();
+ myclass::class_func_static<myclass>();
+}
+
+int main()
+{
+ mytemplateclass<myclass> c;
+ myclass c2;
+ tests_func<myclass>(c, c2);
+
+ c2.testint = 53;
+ /* Make sure this isn't treated as a template. */
+ bool testbool = c2.testint < 999 > 7;
+ /* This probably will be treated as a template initially but it should still work. */
+ testbool = c2.testint < 123 > (50);
+}
diff --git a/gcc/testsuite/g++.dg/template/dependent-name18.C b/gcc/testsuite/g++.dg/template/dependent-name18.C
new file mode 100644
index 00000000000..f6f0a27a0bd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/dependent-name18.C
@@ -0,0 +1,5 @@
+template <bool B> struct A { };
+template <class T> void f()
+{
+ A<T::I < T::J>();
+}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2022-01-13 21:02 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-13 21:02 [gcc r12-6563] c++: warning for dependent template members [PR70417] Jason Merrill
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).